@floless/app 0.15.0 → 0.16.0
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/floless-server.cjs +62 -5
- package/dist/web/app.css +35 -5
- package/dist/web/app.js +16 -3
- package/dist/web/aware.js +52 -3
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -50969,6 +50969,13 @@ function readApp(id) {
|
|
|
50969
50969
|
compilerVersion: typeof lockDoc["compiler-version"] === "string" ? lockDoc["compiler-version"] : null,
|
|
50970
50970
|
agentPins
|
|
50971
50971
|
};
|
|
50972
|
+
const skillCache = /* @__PURE__ */ new Map();
|
|
50973
|
+
const nodeSkill = (agent, command) => {
|
|
50974
|
+
if (!agent || !command) return null;
|
|
50975
|
+
const key = JSON.stringify([agent, command]);
|
|
50976
|
+
if (!skillCache.has(key)) skillCache.set(key, readNodeSkill(agent, command));
|
|
50977
|
+
return skillCache.get(key) ?? null;
|
|
50978
|
+
};
|
|
50972
50979
|
const srcNodes = Array.isArray(src.nodes) ? src.nodes : [];
|
|
50973
50980
|
const nodes = srcNodes.map((n) => {
|
|
50974
50981
|
const nid = String(n.id);
|
|
@@ -50984,7 +50991,8 @@ function readApp(id) {
|
|
|
50984
50991
|
mode: lockMode === "write" || lockMode === "read" ? lockMode : "unknown",
|
|
50985
50992
|
inputs: asRecord(locked?.inputs),
|
|
50986
50993
|
config: asRecord(n.config),
|
|
50987
|
-
notes: notes2
|
|
50994
|
+
notes: notes2,
|
|
50995
|
+
skill: nodeSkill(n.agent != null ? String(n.agent) : null, command)
|
|
50988
50996
|
};
|
|
50989
50997
|
});
|
|
50990
50998
|
const layout = src.layout === "dag" ? "dag" : "linear";
|
|
@@ -51038,7 +51046,7 @@ function sourceNodeId(nodes, connections) {
|
|
|
51038
51046
|
return nodes.find((n) => !hasIncoming.has(n.id))?.id ?? nodes[0].id;
|
|
51039
51047
|
}
|
|
51040
51048
|
function readCommandSpec(agent, command, agentsDir = AGENTS_DIR) {
|
|
51041
|
-
const safe = (n) =>
|
|
51049
|
+
const safe = (n) => !/[/\\\0]/.test(n) && !n.includes("..");
|
|
51042
51050
|
if (!safe(agent) || !safe(command)) return null;
|
|
51043
51051
|
const manifestPath = (0, import_node_path2.join)(agentsDir, agent, "manifest.yaml");
|
|
51044
51052
|
if (!(0, import_node_fs3.existsSync)(manifestPath)) return null;
|
|
@@ -51065,6 +51073,55 @@ function readCommandSpec(agent, command, agentsDir = AGENTS_DIR) {
|
|
|
51065
51073
|
});
|
|
51066
51074
|
return { lifecycle, streaming, inputs };
|
|
51067
51075
|
}
|
|
51076
|
+
function firstParagraph(s) {
|
|
51077
|
+
if (typeof s !== "string") return "";
|
|
51078
|
+
return (s.trim().split(/\n\s*\n/)[0] ?? "").replace(/\s+/g, " ").trim();
|
|
51079
|
+
}
|
|
51080
|
+
function parseManifestInputs(raw) {
|
|
51081
|
+
return Object.entries(asRecord(raw)).map(([name, spec]) => {
|
|
51082
|
+
const s = typeof spec === "string" ? { type: spec } : asRecord(spec);
|
|
51083
|
+
const input = {
|
|
51084
|
+
name,
|
|
51085
|
+
type: typeof s.type === "string" ? s.type : "string",
|
|
51086
|
+
default: "default" in s ? s.default : null,
|
|
51087
|
+
description: typeof s.description === "string" ? s.description : ""
|
|
51088
|
+
};
|
|
51089
|
+
if (Array.isArray(s.values)) input.values = s.values.map(String);
|
|
51090
|
+
return input;
|
|
51091
|
+
});
|
|
51092
|
+
}
|
|
51093
|
+
function readNodeSkill(agent, command, agentsDir = AGENTS_DIR) {
|
|
51094
|
+
const safe = (n) => !/[/\\\0]/.test(n) && !n.includes("..");
|
|
51095
|
+
if (!safe(agent) || !safe(command)) return null;
|
|
51096
|
+
const manifestPath = (0, import_node_path2.join)(agentsDir, agent, "manifest.yaml");
|
|
51097
|
+
if (!(0, import_node_fs3.existsSync)(manifestPath)) return null;
|
|
51098
|
+
let doc;
|
|
51099
|
+
try {
|
|
51100
|
+
doc = asRecord((0, import_yaml.parse)((0, import_node_fs3.readFileSync)(manifestPath, "utf8")));
|
|
51101
|
+
} catch {
|
|
51102
|
+
return null;
|
|
51103
|
+
}
|
|
51104
|
+
const cmd = asRecord(asRecord(doc.commands)[command]);
|
|
51105
|
+
if (Object.keys(cmd).length === 0) return null;
|
|
51106
|
+
const outRec = asRecord(cmd.outputs);
|
|
51107
|
+
const outputs = typeof outRec.type === "string" ? {
|
|
51108
|
+
type: outRec.type,
|
|
51109
|
+
schema: Object.fromEntries(
|
|
51110
|
+
Object.entries(asRecord(outRec.schema)).map(([k, v]) => [k, String(v)])
|
|
51111
|
+
)
|
|
51112
|
+
} : null;
|
|
51113
|
+
return {
|
|
51114
|
+
agent,
|
|
51115
|
+
agentDisplayName: typeof doc["display-name"] === "string" ? doc["display-name"] : agent,
|
|
51116
|
+
agentDescription: firstParagraph(doc.description),
|
|
51117
|
+
homepage: typeof doc.homepage === "string" ? doc.homepage : null,
|
|
51118
|
+
capabilities: Object.keys(asRecord(doc.requires)),
|
|
51119
|
+
command,
|
|
51120
|
+
commandDescription: firstParagraph(cmd.description),
|
|
51121
|
+
inputs: parseManifestInputs(cmd.inputs),
|
|
51122
|
+
outputs
|
|
51123
|
+
};
|
|
51124
|
+
}
|
|
51068
51125
|
function detectTriggerSource(nodes, connections, lookup) {
|
|
51069
51126
|
const srcId = sourceNodeId(nodes, connections);
|
|
51070
51127
|
if (!srcId) return null;
|
|
@@ -51096,7 +51153,7 @@ function listIntegrations() {
|
|
|
51096
51153
|
if (!(0, import_node_fs3.existsSync)(AGENTS_DIR)) return [];
|
|
51097
51154
|
const out = [];
|
|
51098
51155
|
const seen = /* @__PURE__ */ new Set();
|
|
51099
|
-
const safe = (n) =>
|
|
51156
|
+
const safe = (n) => !/[/\\\0]/.test(n) && !n.includes("..");
|
|
51100
51157
|
for (const agentId of (0, import_node_fs3.readdirSync)(AGENTS_DIR)) {
|
|
51101
51158
|
if (!safe(agentId)) continue;
|
|
51102
51159
|
const manifestPath = (0, import_node_path2.join)(AGENTS_DIR, agentId, "manifest.yaml");
|
|
@@ -52609,7 +52666,7 @@ function appVersion() {
|
|
|
52609
52666
|
return resolveVersion({
|
|
52610
52667
|
isSea: isSea2(),
|
|
52611
52668
|
sqVersionXml: readSqVersionXml(),
|
|
52612
|
-
define: true ? "0.
|
|
52669
|
+
define: true ? "0.16.0" : void 0,
|
|
52613
52670
|
pkgVersion: readPkgVersion()
|
|
52614
52671
|
});
|
|
52615
52672
|
}
|
|
@@ -52619,7 +52676,7 @@ function resolveChannel(s) {
|
|
|
52619
52676
|
return "dev";
|
|
52620
52677
|
}
|
|
52621
52678
|
function appChannel() {
|
|
52622
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.
|
|
52679
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.16.0" : void 0 });
|
|
52623
52680
|
}
|
|
52624
52681
|
|
|
52625
52682
|
// oauth-presets.ts
|
package/dist/web/app.css
CHANGED
|
@@ -46,7 +46,10 @@
|
|
|
46
46
|
grid-template-rows: 60px 1fr 44px;
|
|
47
47
|
--left-width: 340px;
|
|
48
48
|
--right-width: 420px;
|
|
49
|
-
|
|
49
|
+
/* minmax(0, 1fr) (not the default minmax(auto, 1fr)) lets the center column
|
|
50
|
+
shrink below its content's min-size, so a wide center never blows the grid
|
|
51
|
+
past the viewport. See min-width:0 on the three areas below. */
|
|
52
|
+
grid-template-columns: var(--left-width) minmax(0, 1fr) var(--right-width);
|
|
50
53
|
grid-template-areas:
|
|
51
54
|
"header header header"
|
|
52
55
|
"chat canvas inspect"
|
|
@@ -104,8 +107,15 @@
|
|
|
104
107
|
justify-content: space-between;
|
|
105
108
|
padding: 0 18px;
|
|
106
109
|
border-bottom: 1px solid var(--border);
|
|
110
|
+
/* Let the header's flex children shrink rather than force the whole grid wider
|
|
111
|
+
than the viewport (the old cause of a page-wide horizontal scrollbar): the
|
|
112
|
+
workflow picker truncates first (down to its 220px floor), the run spine
|
|
113
|
+
compresses only slightly after that, and a scrollbar — never off-screen
|
|
114
|
+
clipping of the run buttons — is the final fallback at tiny widths (#53). */
|
|
115
|
+
min-width: 0;
|
|
107
116
|
}
|
|
108
|
-
|
|
117
|
+
/* Brand + view-toggle stay full size; .controls is the shrink sink instead. */
|
|
118
|
+
.header-left { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
|
|
109
119
|
.brand { display: flex; align-items: center; gap: 12px; }
|
|
110
120
|
.brand .mark {
|
|
111
121
|
display: inline-flex;
|
|
@@ -119,7 +129,7 @@
|
|
|
119
129
|
.brand .mark .mark-node { fill: var(--bg); }
|
|
120
130
|
.brand .name { font-size: 15px; font-weight: 700; letter-spacing: 0.08em; }
|
|
121
131
|
.brand .tag { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.12em; }
|
|
122
|
-
.controls { display: flex; align-items: center; gap: 10px; }
|
|
132
|
+
.controls { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
123
133
|
/* Vertical divider separating "which workflow + reference" from the run spine
|
|
124
134
|
(state → Compile → Simulate → Run). Uses the existing border token. */
|
|
125
135
|
.ctl-sep { width: 1px; height: 20px; background: var(--border-strong); flex: none; }
|
|
@@ -236,6 +246,7 @@
|
|
|
236
246
|
display: flex;
|
|
237
247
|
flex-direction: column;
|
|
238
248
|
overflow: hidden;
|
|
249
|
+
min-width: 0;
|
|
239
250
|
position: relative;
|
|
240
251
|
}
|
|
241
252
|
.host-banner {
|
|
@@ -326,6 +337,7 @@
|
|
|
326
337
|
display: flex;
|
|
327
338
|
flex-direction: column;
|
|
328
339
|
overflow: hidden;
|
|
340
|
+
min-width: 0;
|
|
329
341
|
position: relative;
|
|
330
342
|
}
|
|
331
343
|
/* While middle-mouse panning, force the grab cursor over the whole canvas. */
|
|
@@ -738,6 +750,7 @@
|
|
|
738
750
|
display: flex;
|
|
739
751
|
flex-direction: column;
|
|
740
752
|
overflow: hidden;
|
|
753
|
+
min-width: 0;
|
|
741
754
|
position: relative;
|
|
742
755
|
}
|
|
743
756
|
.tabs {
|
|
@@ -1416,10 +1429,13 @@
|
|
|
1416
1429
|
#rtn-delete-confirm:hover { color: var(--err); border-color: var(--err); background: color-mix(in srgb, var(--err) 10%, transparent); }
|
|
1417
1430
|
|
|
1418
1431
|
/* ========== WORKFLOW COMBOBOX (searchable, provider-grouped) ========== */
|
|
1419
|
-
|
|
1432
|
+
/* Shrinkable in the header flex row: prefers ~300px, but flex-shrink lets it
|
|
1433
|
+
give space back down to a 220px floor (min-width) when the header is tight,
|
|
1434
|
+
so the run spine never overflows the viewport. The name ellipsizes within. */
|
|
1435
|
+
.wf-combo { position: relative; display: inline-flex; flex: 0 1 300px; min-width: 220px; }
|
|
1420
1436
|
.wf-trigger {
|
|
1421
1437
|
display: inline-flex; align-items: center; gap: 8px;
|
|
1422
|
-
min-width:
|
|
1438
|
+
min-width: 0; width: 100%;
|
|
1423
1439
|
background: var(--surface-2); color: var(--text);
|
|
1424
1440
|
border: 1px solid var(--border-strong); border-radius: 4px;
|
|
1425
1441
|
padding: 7px 12px; font-family: var(--ui); font-size: 12px; cursor: pointer;
|
|
@@ -2041,6 +2057,20 @@ body {
|
|
|
2041
2057
|
.kv td { font-family: var(--mono); color: var(--text); word-break: break-word; }
|
|
2042
2058
|
.kv tr + tr th, .kv tr + tr td { border-top: 1px solid var(--border); }
|
|
2043
2059
|
|
|
2060
|
+
/* Skill tab (Inspect): capability/mode pills mirror the notes-strip .note-kind
|
|
2061
|
+
pill, and the agent docs link uses the accent — existing tokens only, no new
|
|
2062
|
+
palette (issue #54). */
|
|
2063
|
+
.inspect-content .cap-badge {
|
|
2064
|
+
display: inline-block; font-size: 9px; text-transform: uppercase; letter-spacing: 0.06em;
|
|
2065
|
+
color: var(--info); border: 1px solid color-mix(in srgb, var(--info) 35%, transparent);
|
|
2066
|
+
border-radius: 3px; padding: 0 5px; margin: 0 2px; vertical-align: 1px;
|
|
2067
|
+
}
|
|
2068
|
+
.inspect-content .cap-badge.write { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
|
|
2069
|
+
/* Breathing room above the docs link — it follows a schema table (no margin-bottom). */
|
|
2070
|
+
.inspect-content .skill-docs-row { margin-top: 8px; }
|
|
2071
|
+
.inspect-content .skill-docs { color: var(--accent); text-decoration: none; }
|
|
2072
|
+
.inspect-content .skill-docs:hover { text-decoration: underline; }
|
|
2073
|
+
|
|
2044
2074
|
/* ── HTML Viewer (report) modal ─────────────────────────────────────────────── */
|
|
2045
2075
|
.modal.report-viewer {
|
|
2046
2076
|
width: 1180px; max-width: 94vw; height: 86vh;
|
package/dist/web/app.js
CHANGED
|
@@ -354,7 +354,7 @@ function cardEl(id, ports) {
|
|
|
354
354
|
`;
|
|
355
355
|
div.onclick = (e) => {
|
|
356
356
|
if (e.target.closest('.fav-btn')) return;
|
|
357
|
-
selectAgent(id);
|
|
357
|
+
selectAgent(id, { reveal: true }); // clicking the card (incl. "inspect ▸") opens the panel if collapsed
|
|
358
358
|
};
|
|
359
359
|
div.querySelector('.fav-btn').onclick = (e) => {
|
|
360
360
|
e.stopPropagation();
|
|
@@ -379,13 +379,26 @@ function wireEl(idx, label) {
|
|
|
379
379
|
return wrap;
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
-
|
|
382
|
+
// `reveal` is set by USER-initiated selections (clicking a node card / the
|
|
383
|
+
// "inspect ▸" hint / a favorite chip). When the Inspect panel is collapsed those
|
|
384
|
+
// would otherwise give zero visible feedback — the panel quietly re-renders behind
|
|
385
|
+
// the rail (issue #52) — so a revealing selection also expands it. Programmatic /
|
|
386
|
+
// initial selection (on render) passes no reveal, so a saved collapsed preference
|
|
387
|
+
// is respected on load.
|
|
388
|
+
function selectAgent(id, { reveal = false } = {}) {
|
|
383
389
|
state.selectedAgentId = id;
|
|
384
390
|
document.querySelectorAll('.agent-card').forEach(c => {
|
|
385
391
|
c.classList.toggle('selected', c.dataset.agentId === id);
|
|
386
392
|
});
|
|
387
393
|
const a = AGENTS[id];
|
|
388
394
|
if (a) $inspectRole.textContent = a.title.toLowerCase();
|
|
395
|
+
if (reveal && state.collapse.right) {
|
|
396
|
+
// Momentary reveal — expand the panel for THIS interaction but do NOT persist:
|
|
397
|
+
// the user collapsed it deliberately, so a single inspect must not overwrite
|
|
398
|
+
// that saved preference (only an explicit toggle/drag does). UX review on #52.
|
|
399
|
+
state.collapse.right = false;
|
|
400
|
+
applyCollapse();
|
|
401
|
+
}
|
|
389
402
|
renderInspect();
|
|
390
403
|
}
|
|
391
404
|
|
|
@@ -624,7 +637,7 @@ function renderFavBar() {
|
|
|
624
637
|
const id = chip.dataset.favAgent;
|
|
625
638
|
const p = PROMPTS[state.promptKey];
|
|
626
639
|
if (nodeIds(p).includes(id)) {
|
|
627
|
-
selectAgent(id);
|
|
640
|
+
selectAgent(id, { reveal: true });
|
|
628
641
|
} else {
|
|
629
642
|
chip.style.borderColor = 'var(--warn)';
|
|
630
643
|
setTimeout(() => chip.style.borderColor = '', 600);
|
package/dist/web/aware.js
CHANGED
|
@@ -144,6 +144,47 @@
|
|
|
144
144
|
.join('')}</table>`;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
// Plain-text summary of one declared command input (for the Skill tab's inputs
|
|
148
|
+
// table — kvTable escapes it, so this stays text, no markup): "type (enum vals)
|
|
149
|
+
// · default X — description".
|
|
150
|
+
function inputSummary(i) {
|
|
151
|
+
let s = i.type || 'string';
|
|
152
|
+
if (i.values && i.values.length) s += ` (${i.values.join(' · ')})`;
|
|
153
|
+
if (i.default !== null && i.default !== undefined && i.default !== '') {
|
|
154
|
+
s += ` · default ${typeof i.default === 'string' ? i.default : JSON.stringify(i.default)}`;
|
|
155
|
+
}
|
|
156
|
+
if (i.description) s += ` — ${i.description}`;
|
|
157
|
+
return s;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Inspect → Skill tab body. Rich when the agent manifest is installed (n.skill):
|
|
161
|
+
// what the agent + command do, capabilities, the command's DECLARED inputs and
|
|
162
|
+
// output schema, and a docs link. Falls back to the lock-pointer summary for
|
|
163
|
+
// exec/agent-less nodes or an uninstalled agent. DISPLAY of manifest data only —
|
|
164
|
+
// every file-derived string is escaped (kvTable/escapeHtml), never injected raw.
|
|
165
|
+
function skillBody(n, fallback, notesHtml) {
|
|
166
|
+
const sk = n.skill;
|
|
167
|
+
if (!sk) return fallback; // fallback already carries notesHtml
|
|
168
|
+
const modeBadge = `<span class="cap-badge${n.mode === 'write' ? ' write' : ''}">${escapeHtml(n.mode)}</span>`;
|
|
169
|
+
const capBadges = sk.capabilities.map((c) => `<span class="cap-badge">${escapeHtml(c)}</span>`).join(' ');
|
|
170
|
+
const inputsObj = Object.fromEntries(sk.inputs.map((i) => [i.name, inputSummary(i)]));
|
|
171
|
+
const docs = sk.homepage && /^https?:\/\//i.test(sk.homepage)
|
|
172
|
+
? `<p class="skill-docs-row"><a class="skill-docs" href="${escapeHtml(sk.homepage)}" target="_blank" rel="noopener">Agent documentation ↗</a></p>`
|
|
173
|
+
: '';
|
|
174
|
+
const outputs = sk.outputs
|
|
175
|
+
? `<p><strong>Returns</strong> · ${escapeHtml(sk.outputs.type)}</p>${Object.keys(sk.outputs.schema).length ? kvTable(sk.outputs.schema) : ''}`
|
|
176
|
+
: '';
|
|
177
|
+
return `
|
|
178
|
+
<h3>${escapeHtml(sk.agentDisplayName)}</h3>
|
|
179
|
+
${sk.agentDescription ? `<p>${escapeHtml(sk.agentDescription)}</p>` : ''}
|
|
180
|
+
<h3>Command · <code>${escapeHtml(sk.command)}</code></h3>
|
|
181
|
+
${sk.commandDescription ? `<p>${escapeHtml(sk.commandDescription)}</p>` : ''}
|
|
182
|
+
<p><strong>Mode</strong> ${modeBadge}${capBadges ? ' · <strong>Requires</strong> ' + capBadges : ''}</p>
|
|
183
|
+
<p><strong>Accepts</strong></p>${Object.keys(inputsObj).length ? kvTable(inputsObj) : '<p class="dim-note">No declared inputs.</p>'}
|
|
184
|
+
${outputs}
|
|
185
|
+
${docs}${notesHtml || ''}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
147
188
|
// Dependency-free C# highlighter for the Code tab. Single-pass scanner that
|
|
148
189
|
// handles comments/strings/chars BEFORE keywords (so keywords inside strings
|
|
149
190
|
// aren't recolored), escapes every token's text, and reuses the demo's token
|
|
@@ -250,8 +291,14 @@
|
|
|
250
291
|
subtitle: `${agentLabel}${cmd ? '/' + cmd : ''} · ${mode}`,
|
|
251
292
|
blurb: n.notes[0] ? escapeHtml(humanizeNote(n.notes[0].text)) : `${mode}-mode ${cmd ? '<code>' + cmd + '</code>' : escapeHtml(n.kind)} node`,
|
|
252
293
|
description: `${plainDesc ? `<p>${escapeHtml(plainDesc)}</p>` : ''}<p>Resolves to ${modeBadge} via <code>${escapeHtml(n.agent || n.kind)}${n.command ? '.' + escapeHtml(n.command) : ''}</code>.</p>${Object.keys(inputsNoCode).length ? `<p><strong>Inputs</strong></p>${kvTable(inputsNoCode)}` : ''}${execSource ? `<p class="dim-note">Full source in the <strong>Code</strong> tab.</p>` : ''}${notesHtml}`,
|
|
253
|
-
|
|
294
|
+
// Rich agent/command detail when the manifest is installed (n.skill); else
|
|
295
|
+
// the lock-pointer fallback below (exec/agent-less nodes, uninstalled agents).
|
|
296
|
+
skill: skillBody(
|
|
297
|
+
n,
|
|
298
|
+
`<h3>Provided by</h3><p><code>${escapeHtml(n.agent || n.kind)}</code>${pin ? ' · pinned <code>v' + escapeHtml(pin) + '</code>' : ''}${n.command ? ' · command <code>' + escapeHtml(n.command) + '</code>' : ''}</p>
|
|
254
299
|
<p>Resolved write/read mode is part of the approved <code>.lock</code> — see the <strong>Code</strong> tab.</p>${notesHtml}`,
|
|
300
|
+
notesHtml,
|
|
301
|
+
),
|
|
255
302
|
// Code tab: the REAL node source. For exec nodes (Roslyn C# run against a
|
|
256
303
|
// live host) that's `config.code` straight from the .flo — the same text
|
|
257
304
|
// the host compiles, so "Debug in VS" attaches to exactly this. For
|
|
@@ -3295,8 +3342,10 @@
|
|
|
3295
3342
|
// button only for exec nodes (those carry C# the host can run under a debugger).
|
|
3296
3343
|
// Debug lives HERE in the main window, not in the report modal.
|
|
3297
3344
|
const _selectAgent = selectAgent;
|
|
3298
|
-
|
|
3299
|
-
|
|
3345
|
+
// Forward ALL args (id + the { reveal } opts from app.js) — dropping them here
|
|
3346
|
+
// silently swallowed the collapsed-panel reveal on inspect (issue #52).
|
|
3347
|
+
selectAgent = function selectAgentTweakAware(id, ...rest) {
|
|
3348
|
+
_selectAgent(id, ...rest);
|
|
3300
3349
|
const tb = document.getElementById('tweak-btn');
|
|
3301
3350
|
if (tb) tb.hidden = !id;
|
|
3302
3351
|
const db = document.getElementById('debug-btn');
|