@agent-plan/pi-adapter 0.2.7 → 0.2.9
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -218
- package/package.json +1 -1
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AA4yBtF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmrF9D"}
|
package/dist/index.js
CHANGED
|
@@ -111,8 +111,8 @@ const PLANNER_COMMAND_COMPLETIONS = [
|
|
|
111
111
|
{ value: "export-full", label: "export-full", description: "Export full detailed plan as Markdown" },
|
|
112
112
|
{ value: "bypass", label: "bypass", description: "Authorize edit/write without a task in-progress (15 min)" },
|
|
113
113
|
{ value: "clear-bypass", label: "clear-bypass", description: "Revoke the guard bypass" },
|
|
114
|
-
{ value: "load", label: "load", description: "
|
|
115
|
-
{ value: "
|
|
114
|
+
{ value: "load", label: "load", description: "Enable planner + start web UI (LAN) and show resume summary" },
|
|
115
|
+
{ value: "stop", label: "stop", description: "Disable planner and shut down the web UI" },
|
|
116
116
|
];
|
|
117
117
|
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
118
118
|
function nowISO() {
|
|
@@ -165,24 +165,6 @@ async function maybeHealStatuses(st) {
|
|
|
165
165
|
function normalizeLanguagePref(value) {
|
|
166
166
|
return value?.trim() ?? "";
|
|
167
167
|
}
|
|
168
|
-
// Safety net: ctx.ui.input() can hang forever when the extension is loaded from
|
|
169
|
-
// an NPM-installed package (the prompt never renders, the await never resolves,
|
|
170
|
-
// and Pi freezes). Race the prompt against a timeout; on timeout, fall back to the
|
|
171
|
-
// empty answer (treated as "no / this session only" by the gating logic) and notify
|
|
172
|
-
// the user how to enable manually. This preserves the interactive prompt for the
|
|
173
|
-
// working case while guaranteeing session_start can never block Pi permanently.
|
|
174
|
-
const PROMPT_TIMEOUT_MS = 20000;
|
|
175
|
-
async function safeInput(ctx, prompt) {
|
|
176
|
-
try {
|
|
177
|
-
return await Promise.race([
|
|
178
|
-
ctx.ui.input(prompt),
|
|
179
|
-
new Promise((resolve) => setTimeout(() => resolve("__PLANNER_PROMPT_TIMEOUT__"), PROMPT_TIMEOUT_MS)),
|
|
180
|
-
]);
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
return "";
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
168
|
async function ensureProjectLanguagePreferences(st) {
|
|
187
169
|
const project = await st.loadProject();
|
|
188
170
|
const contentLanguage = normalizeLanguagePref(project.contentLanguage);
|
|
@@ -661,16 +643,14 @@ async function maybeStartWeb(ctx) {
|
|
|
661
643
|
plannerSessionEnabled = true;
|
|
662
644
|
return;
|
|
663
645
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
ctx
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
ctx.ui.notify(`Failed to start web server: ${String(err)}`, "error");
|
|
673
|
-
}
|
|
646
|
+
ctx.ui.notify("Starting web server (LAN)…", "info");
|
|
647
|
+
try {
|
|
648
|
+
await startServer(ctx, undefined, "lan");
|
|
649
|
+
const srv = server;
|
|
650
|
+
ctx.ui.notify(srv?.lanUrl ? `Web UI ready. Local: ${srv.localUrl} — LAN: ${srv.lanUrl}` : `Web UI ready. Open: ${srv?.url ?? "?"}`, "info");
|
|
651
|
+
}
|
|
652
|
+
catch (err) {
|
|
653
|
+
ctx.ui.notify(`Failed to start web server: ${String(err)}`, "error");
|
|
674
654
|
}
|
|
675
655
|
}
|
|
676
656
|
function normalizeVisibility(input) {
|
|
@@ -683,17 +663,7 @@ function normalizeVisibility(input) {
|
|
|
683
663
|
return "local";
|
|
684
664
|
return undefined;
|
|
685
665
|
}
|
|
686
|
-
async function
|
|
687
|
-
try {
|
|
688
|
-
const ans = await ctx.ui.input("Web UI visibility: (local) only this machine / (lan) visible on the local network? [local/lan]");
|
|
689
|
-
const v = normalizeVisibility(ans);
|
|
690
|
-
if (v)
|
|
691
|
-
return v;
|
|
692
|
-
}
|
|
693
|
-
catch { }
|
|
694
|
-
return "local";
|
|
695
|
-
}
|
|
696
|
-
async function startServer(ctx, requestedPort, visibility = "local") {
|
|
666
|
+
async function startServer(ctx, requestedPort, visibility = "lan") {
|
|
697
667
|
plannerSessionEnabled = true;
|
|
698
668
|
if (server)
|
|
699
669
|
return;
|
|
@@ -819,61 +789,14 @@ export default function planPiExtension(pi) {
|
|
|
819
789
|
const exists = await st.exists().catch(() => false);
|
|
820
790
|
let project = exists ? await st.loadProject().catch(() => null) : null;
|
|
821
791
|
// ── Step 1: Enable gating ───────────────────────────────────────
|
|
792
|
+
// No blocking prompt in session_start: ctx.ui.input()/select() do not
|
|
793
|
+
// render for NPM-loaded ESM extensions during session_start (they hang
|
|
794
|
+
// until the safety-net timeout). The planner is always DISABLED at
|
|
795
|
+
// startup; the user enables it with '/planner load' (which also starts
|
|
796
|
+
// the web UI on LAN and triggers the resume summary showing the URL).
|
|
822
797
|
let enablePlanner = false;
|
|
823
798
|
if (exists) {
|
|
824
|
-
|
|
825
|
-
enablePlanner = true; // 'always' persisted — no prompt
|
|
826
|
-
}
|
|
827
|
-
else if (project?.plannerNeverAsk) {
|
|
828
|
-
enablePlanner = false; // 'never' persisted — no prompt, no activation
|
|
829
|
-
}
|
|
830
|
-
else {
|
|
831
|
-
try {
|
|
832
|
-
const ans = await safeInput(ctx, "Planner detected in this project. Enable the planner extension? (y)es / (n)o / (a)lways / n(e)ver)");
|
|
833
|
-
const normalized = ans?.trim().toLowerCase() ?? "";
|
|
834
|
-
if (["a", "always", "sempre"].includes(normalized)) {
|
|
835
|
-
enablePlanner = true;
|
|
836
|
-
if (project) {
|
|
837
|
-
project.plannerAutoEnable = true;
|
|
838
|
-
project.plannerNeverAsk = false;
|
|
839
|
-
await st.saveProject(project);
|
|
840
|
-
}
|
|
841
|
-
ctx.ui.notify("Saved: planner will auto-enable for this project.", "info");
|
|
842
|
-
}
|
|
843
|
-
else if (["e", "never", "mai"].includes(normalized)) {
|
|
844
|
-
enablePlanner = false;
|
|
845
|
-
if (project) {
|
|
846
|
-
project.plannerNeverAsk = true;
|
|
847
|
-
project.plannerAutoEnable = false;
|
|
848
|
-
await st.saveProject(project);
|
|
849
|
-
}
|
|
850
|
-
try {
|
|
851
|
-
capturedPi?.appendEntry("plan-web-state", { running: false });
|
|
852
|
-
}
|
|
853
|
-
catch { }
|
|
854
|
-
ctx.ui.notify("Saved: planner will not ask again for this project. Use '/planner load' to re-enable manually.", "info");
|
|
855
|
-
}
|
|
856
|
-
else if (/^(y|yes|s|si|sì)$/i.test(normalized)) {
|
|
857
|
-
enablePlanner = true; // this session only
|
|
858
|
-
}
|
|
859
|
-
else {
|
|
860
|
-
// 'n' / 'no' / empty — this session only, NOT persisted
|
|
861
|
-
enablePlanner = false;
|
|
862
|
-
try {
|
|
863
|
-
capturedPi?.appendEntry("plan-web-state", { running: false });
|
|
864
|
-
}
|
|
865
|
-
catch { }
|
|
866
|
-
ctx.ui.notify("Planner disabled for this session. Use '/planner load' to enable manually.", "info");
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
catch {
|
|
870
|
-
enablePlanner = false;
|
|
871
|
-
try {
|
|
872
|
-
capturedPi?.appendEntry("plan-web-state", { running: false });
|
|
873
|
-
}
|
|
874
|
-
catch { }
|
|
875
|
-
}
|
|
876
|
-
}
|
|
799
|
+
ctx.ui.notify("Planner detected in this project. Run '/planner load' to enable the planner and start the web UI (LAN).", "info");
|
|
877
800
|
}
|
|
878
801
|
plannerSessionEnabled = enablePlanner;
|
|
879
802
|
// Proactive check for leftover handoff files.
|
|
@@ -889,95 +812,6 @@ export default function planPiExtension(pi) {
|
|
|
889
812
|
startupResumeSummaryPending = false;
|
|
890
813
|
return;
|
|
891
814
|
}
|
|
892
|
-
// ── Step 2: Web UI gating (only if planner enabled) ─────────────
|
|
893
|
-
startupResumePromptPending = exists && plannerSessionEnabled;
|
|
894
|
-
startupResumeSummaryPending = exists && plannerSessionEnabled;
|
|
895
|
-
if (exists && plannerSessionEnabled) {
|
|
896
|
-
// Migration first.
|
|
897
|
-
try {
|
|
898
|
-
await migrateToUuids(st);
|
|
899
|
-
}
|
|
900
|
-
catch (e) {
|
|
901
|
-
ctx.ui.notify(`Migration failed: ${e instanceof Error ? e.message : String(e)}`, "error");
|
|
902
|
-
}
|
|
903
|
-
await st.ensureStructureOrdering().catch(() => { });
|
|
904
|
-
// Reconcile derived statuses once on startup: backfills any drift from
|
|
905
|
-
// manual edits, pre-fix data, or tools that bypassed the rollup.
|
|
906
|
-
// Uses maybeHealStatuses (idempotent via healedStatusRoots guard) so the
|
|
907
|
-
// later call below and before_agent_start don't re-run it (startup dedup).
|
|
908
|
-
await maybeHealStatuses(st).catch(() => { });
|
|
909
|
-
// Decide whether to start the web UI based on persisted prefs or prompt.
|
|
910
|
-
let startWeb = false;
|
|
911
|
-
if (project?.plannerAutoStartWeb) {
|
|
912
|
-
startWeb = true; // 'always' persisted
|
|
913
|
-
}
|
|
914
|
-
else if (project?.plannerNeverStartWeb) {
|
|
915
|
-
startWeb = false; // 'never' persisted
|
|
916
|
-
}
|
|
917
|
-
else if (server === null) {
|
|
918
|
-
try {
|
|
919
|
-
const ans = await safeInput(ctx, "Start the planner web UI? (y)es / (n)o / (a)lways / n(e)ver)");
|
|
920
|
-
const normalized = ans?.trim().toLowerCase() ?? "";
|
|
921
|
-
if (["a", "always", "sempre"].includes(normalized)) {
|
|
922
|
-
startWeb = true;
|
|
923
|
-
if (project) {
|
|
924
|
-
project.plannerAutoStartWeb = true;
|
|
925
|
-
project.plannerNeverStartWeb = false;
|
|
926
|
-
await st.saveProject(project);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
else if (["e", "never", "mai"].includes(normalized)) {
|
|
930
|
-
startWeb = false;
|
|
931
|
-
if (project) {
|
|
932
|
-
project.plannerNeverStartWeb = true;
|
|
933
|
-
project.plannerAutoStartWeb = false;
|
|
934
|
-
await st.saveProject(project);
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
else if (/^(y|yes|s|si|sì)$/i.test(normalized)) {
|
|
938
|
-
startWeb = true;
|
|
939
|
-
}
|
|
940
|
-
else {
|
|
941
|
-
startWeb = false; // 'n' / 'no' / empty — this session only
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
catch {
|
|
945
|
-
startWeb = false;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
if (startWeb && server === null) {
|
|
949
|
-
const visibility = await promptWebVisibility(ctx);
|
|
950
|
-
await startServer(ctx, preferredPort, visibility).catch(() => { });
|
|
951
|
-
const srv = server;
|
|
952
|
-
if (srv) {
|
|
953
|
-
const urls = srv.lanUrl ? `${srv.localUrl} (LAN: ${srv.lanUrl})` : srv.url;
|
|
954
|
-
ctx.ui.notify(`Web UI started at ${urls}`, "info");
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
const url = server?.url ?? "Starting server...";
|
|
958
|
-
ctx.ui.notify(`Planner enabled. Dashboard: ${url}\nAnalyzing project state and preparing resume summary...`, "info");
|
|
959
|
-
await ensureProjectLanguagePreferences(st).catch(() => null);
|
|
960
|
-
// (maybeHealStatuses already ran above — skipped here thanks to the guard.)
|
|
961
|
-
// Background cleanup of orphan .bak/.tmp.* files (non-blocking)
|
|
962
|
-
st.cleanupOrphanBackups().catch(() => { });
|
|
963
|
-
const handoff = await st.loadHandoff().catch(() => null);
|
|
964
|
-
if (handoff?.content) {
|
|
965
|
-
ctx.ui.notify(`Handoff detected at .planner/HANDOFF.md (updated ${handoff.updatedAt}). It will be loaded automatically when the agent starts.`, "info");
|
|
966
|
-
}
|
|
967
|
-
// Mark heavy init done so before_agent_start doesn't re-run migration,
|
|
968
|
-
// status healing, and language prefs on the first turn (startup dedup:
|
|
969
|
-
// all of these already ran in this session_start block). refreshResume is
|
|
970
|
-
// safe to skip here because the per-turn context builder falls back to
|
|
971
|
-
// loadResume() ?? refreshResume() when building the system prompt.
|
|
972
|
-
plannerHeavyInitDone = true;
|
|
973
|
-
pi.sendMessage({
|
|
974
|
-
customType: "planner-resume-trigger",
|
|
975
|
-
content: "Emit the mandatory planner startup resume summary now.",
|
|
976
|
-
display: false,
|
|
977
|
-
}, {
|
|
978
|
-
triggerTurn: true,
|
|
979
|
-
});
|
|
980
|
-
}
|
|
981
815
|
}
|
|
982
816
|
catch (e) {
|
|
983
817
|
// Defensive: never let session_start throw crash Pi silently.
|
|
@@ -1158,9 +992,9 @@ export default function planPiExtension(pi) {
|
|
|
1158
992
|
"bypass",
|
|
1159
993
|
"clear-bypass",
|
|
1160
994
|
"load",
|
|
1161
|
-
"
|
|
995
|
+
"stop",
|
|
1162
996
|
];
|
|
1163
|
-
const SUB_HELP = "Available: init, show, repair, project, feature, phase, task, discuss, handoff, web, export, export-full, bypass, clear-bypass, load,
|
|
997
|
+
const SUB_HELP = "Available: init, show, repair, project, feature, phase, task, discuss, handoff, web, export, export-full, bypass, clear-bypass, load, stop\n" +
|
|
1164
998
|
"Try: /planner <TAB> | /planner feature list | /planner task start | /planner export-full\n" +
|
|
1165
999
|
"Handoff actions: /planner handoff prepare | show | write | clear";
|
|
1166
1000
|
if (!a) {
|
|
@@ -2345,7 +2179,7 @@ export default function planPiExtension(pi) {
|
|
|
2345
2179
|
}
|
|
2346
2180
|
const visibilityArg = normalizeVisibility(parts[2]);
|
|
2347
2181
|
const portArg = parts[2]?.trim() && Number.isFinite(parseInt(parts[2], 10)) ? parseInt(parts[2], 10) : undefined;
|
|
2348
|
-
const visibility = visibilityArg ??
|
|
2182
|
+
const visibility = visibilityArg ?? "lan";
|
|
2349
2183
|
ctx.ui.notify(`Starting web server (${visibility})${portArg ? ` on port ${portArg}` : ""} …`, "info");
|
|
2350
2184
|
await startServer(ctx, portArg, visibility);
|
|
2351
2185
|
const srv = server;
|
|
@@ -2418,46 +2252,34 @@ export default function planPiExtension(pi) {
|
|
|
2418
2252
|
return;
|
|
2419
2253
|
}
|
|
2420
2254
|
if (a === "load") {
|
|
2421
|
-
const project = await st.loadProject().catch(() => null);
|
|
2422
|
-
if (project) {
|
|
2423
|
-
let changed = false;
|
|
2424
|
-
if (project.plannerNeverAsk) {
|
|
2425
|
-
project.plannerNeverAsk = false;
|
|
2426
|
-
changed = true;
|
|
2427
|
-
}
|
|
2428
|
-
if (project.plannerNeverStartWeb) {
|
|
2429
|
-
project.plannerNeverStartWeb = false;
|
|
2430
|
-
changed = true;
|
|
2431
|
-
}
|
|
2432
|
-
if (changed) {
|
|
2433
|
-
await st.saveProject(project);
|
|
2434
|
-
ctx.ui.notify("Cleared 'never' flags. Planner enabled for this session.", "info");
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
2255
|
plannerSessionEnabled = true;
|
|
2438
2256
|
if (!server) {
|
|
2439
|
-
ctx.ui.notify("Starting web server …", "info");
|
|
2440
|
-
await startServer(ctx);
|
|
2441
|
-
}
|
|
2442
|
-
|
|
2257
|
+
ctx.ui.notify("Starting web server (LAN) …", "info");
|
|
2258
|
+
await startServer(ctx, undefined, "lan").catch(() => { });
|
|
2259
|
+
}
|
|
2260
|
+
const srv = server;
|
|
2261
|
+
ctx.ui.notify(srv?.lanUrl ? `Planner loaded. Web UI: ${srv.localUrl} (LAN: ${srv.lanUrl})` : `Planner loaded. Web UI: ${srv?.url ?? "(not started)"}`, "info");
|
|
2262
|
+
// Trigger the mandatory startup resume summary so the agent surfaces the
|
|
2263
|
+
// web UI address (and project state) immediately after loading.
|
|
2264
|
+
startupResumePromptPending = true;
|
|
2265
|
+
startupResumeSummaryPending = true;
|
|
2266
|
+
pi.sendMessage({
|
|
2267
|
+
customType: "planner-resume-trigger",
|
|
2268
|
+
content: "Emit the mandatory planner startup resume summary now (include the web UI address and port).",
|
|
2269
|
+
display: false,
|
|
2270
|
+
}, {
|
|
2271
|
+
triggerTurn: true,
|
|
2272
|
+
});
|
|
2443
2273
|
return;
|
|
2444
2274
|
}
|
|
2445
|
-
if (a === "disable") {
|
|
2446
|
-
const project = await st.loadProject().catch(() => null);
|
|
2447
|
-
if (project) {
|
|
2448
|
-
project.plannerAutoEnable = false;
|
|
2449
|
-
project.plannerNeverAsk = false;
|
|
2450
|
-
project.plannerAutoStartWeb = false;
|
|
2451
|
-
project.plannerNeverStartWeb = false;
|
|
2452
|
-
await st.saveProject(project);
|
|
2453
|
-
}
|
|
2275
|
+
if (a === "stop" || a === "disable") {
|
|
2454
2276
|
plannerSessionEnabled = false;
|
|
2455
2277
|
await stopServer().catch(() => { });
|
|
2456
2278
|
try {
|
|
2457
2279
|
capturedPi?.appendEntry("plan-web-state", { running: false });
|
|
2458
2280
|
}
|
|
2459
2281
|
catch { }
|
|
2460
|
-
ctx.ui.notify("Planner
|
|
2282
|
+
ctx.ui.notify("Planner stopped. Web UI shut down. Run '/planner load' to re-enable.", "info");
|
|
2461
2283
|
return;
|
|
2462
2284
|
}
|
|
2463
2285
|
ctx.ui.notify(`Unknown "${a}". ${SUB_HELP}`, "warning");
|
|
@@ -3649,7 +3471,18 @@ export default function planPiExtension(pi) {
|
|
|
3649
3471
|
: (handoff?.updatedAt
|
|
3650
3472
|
? "Read the handoff, then validate current plan ordering and dependencies before picking the next task."
|
|
3651
3473
|
: "Review the current plan and choose the next task.");
|
|
3652
|
-
const
|
|
3474
|
+
const webServer = server;
|
|
3475
|
+
const webLocalUrl = webServer?.localUrl ?? webServer?.url ?? "";
|
|
3476
|
+
const webLanUrl = webServer?.lanUrl ?? "";
|
|
3477
|
+
let webPort = "";
|
|
3478
|
+
try {
|
|
3479
|
+
const p = new URL(webLocalUrl).port;
|
|
3480
|
+
if (p)
|
|
3481
|
+
webPort = p;
|
|
3482
|
+
}
|
|
3483
|
+
catch { }
|
|
3484
|
+
const webUrl = webLocalUrl;
|
|
3485
|
+
const webUrlFull = webLanUrl ? `${webLocalUrl} (LAN: ${webLanUrl})` : webLocalUrl;
|
|
3653
3486
|
const openQuestions = plan.phases
|
|
3654
3487
|
.flatMap((p) => p.openQuestions.map((q) => `[${p.id}] ${q}`))
|
|
3655
3488
|
.slice(0, 12);
|
|
@@ -3679,7 +3512,7 @@ export default function planPiExtension(pi) {
|
|
|
3679
3512
|
currentPhase ? `- Mention current phase ONLY because it is actually active: ${currentPhase.id} — ${phaseLabel(currentPhase)} (${currentPhase.status}).` : "- Mention that no current phase is active.",
|
|
3680
3513
|
currentTask ? `- Mention current task ONLY because it is actually active: ${currentTask.id} — ${taskLabel(currentTask)} (${currentTask.status}).` : "- Mention that no current task is active.",
|
|
3681
3514
|
!hasActiveWork ? "- If a handoff exists, describe it only as a previous-session hint to validate against current planner state, ordering, and dependencies. Do NOT present it as the current focus." : "",
|
|
3682
|
-
webUrl ? `-
|
|
3515
|
+
webUrl ? `- At the END of your summary, you MUST print a dedicated line with the web dashboard address and port, exactly: '🌐 Web UI: ${webLocalUrl}${webPort ? ` (port ${webPort})` : ""}${webLanUrl ? ` — LAN: ${webLanUrl}` : ""}'. This line is mandatory; do not omit it.` : "- Explicitly tell the user whether the web dashboard is active in this session (and how to start it with '/planner web start' if not).",
|
|
3683
3516
|
`- Mention the next suggested activity: ${nextActivity}`,
|
|
3684
3517
|
"- THEN ask the user explicitly whether they want to resume that activity now.",
|
|
3685
3518
|
"- Do NOT assume yes. Wait for the user's answer before continuing implementation work.",
|
|
@@ -3688,6 +3521,9 @@ export default function planPiExtension(pi) {
|
|
|
3688
3521
|
// Build/refresh the context block (slow path: cache miss).
|
|
3689
3522
|
const contextBlock = [
|
|
3690
3523
|
`[Plan Context — ${project.name}]`,
|
|
3524
|
+
// ── Web UI status (prominent — agent must surface URL in its summary) ──
|
|
3525
|
+
webUrl ? `🌐 WEB UI RUNNING: ${webUrlFull}${webPort ? ` (port ${webPort})` : ""}. Include this address+port at the end of your resume summary.` : `Web UI: not running. Start it with '/planner web start'.`,
|
|
3526
|
+
"",
|
|
3691
3527
|
// ── MANDATORY OPERATIONAL PROTOCOL (top priority, non-negotiable) ──
|
|
3692
3528
|
"",
|
|
3693
3529
|
"═══════════════════════════════════════════════════════════════",
|