comfyui-mcp 0.48.28 → 0.48.30
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.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/panel-tools.js +274 -14
- package/dist/orchestrator/panel-tools.js.map +1 -1
- package/dist/services/graph-query.js +172 -13
- package/dist/services/graph-query.js.map +1 -1
- package/dist/services/panel-settings.js +11 -2
- package/dist/services/panel-settings.js.map +1 -1
- package/dist/services/ui-bridge.js +120 -16
- package/dist/services/ui-bridge.js.map +1 -1
- package/dist/tools/compact.js +30 -4
- package/dist/tools/compact.js.map +1 -1
- package/dist/tools/index.js +59 -0
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
import { ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
-
import {
|
|
6
|
+
import { collectToolCatalog, registerFullTools } from "./tools/index.js";
|
|
7
7
|
import { registerCompactTools } from "./tools/compact.js";
|
|
8
8
|
import { logger } from "./utils/logger.js";
|
|
9
9
|
import { JobWatcher } from "./services/job-watcher.js";
|
|
@@ -102,7 +102,11 @@ async function createConfiguredServer(toolMode = "full") {
|
|
|
102
102
|
logger.info(`Compact tool mode: ${catalog.tools.size} tools available via list_tools/describe_tool/call_tool`);
|
|
103
103
|
}
|
|
104
104
|
else {
|
|
105
|
-
|
|
105
|
+
// Full direct surface PLUS the compact facade (list_tools/describe_tool/
|
|
106
|
+
// call_tool) as a stable reconnect escape hatch — see registerFullTools and
|
|
107
|
+
// issue #616. One atomic registration pass, completed before the transport
|
|
108
|
+
// connects below. Opt out of the facade with COMFYUI_MCP_NO_FACADE=1.
|
|
109
|
+
await registerFullTools(server);
|
|
106
110
|
}
|
|
107
111
|
server.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
108
112
|
resources: [],
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,kCAAkC,GACnC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,7 +30,7 @@ import { fileURLToPath } from "node:url";
|
|
|
30
30
|
import { comfyuiFetch } from "../comfyui/fetch.js";
|
|
31
31
|
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
32
32
|
import { parse as parseYaml } from "yaml";
|
|
33
|
-
import { dispatchOutcomeOf, isPanelCmdUnsupportedError } from "../services/ui-bridge.js";
|
|
33
|
+
import { dispatchOutcomeOf, isPanelCmdUnsupportedError, isReplyTimeoutTagged, } from "../services/ui-bridge.js";
|
|
34
34
|
import { withWorkflowTarget, } from "../services/workflow-target-store.js";
|
|
35
35
|
import { addUserMcpServer, readUserMcpServers, removeUserMcpServer, setUserMcpServerSecret, } from "../services/user-mcp-config.js";
|
|
36
36
|
import { setComfyuiSecret, setAgentSecret, isAllowedAgentSecretKey } from "../services/panel-secrets.js";
|
|
@@ -43,12 +43,65 @@ import { restartComfyUI } from "../services/process-control.js";
|
|
|
43
43
|
import { isRemoteMode, isCloudMode, getBootLocalComfyUIBaseUrl, getComfyUIBaseUrl, } from "../config.js";
|
|
44
44
|
import { sliceWorkflow } from "../services/workflow-slicer.js";
|
|
45
45
|
import { validateA2UISpecServer } from "../services/a2ui-spec.js";
|
|
46
|
-
/** Treat these as an affirmative answer to
|
|
46
|
+
/** Treat these as an affirmative answer to a yes/no confirm card (destructive-op
|
|
47
|
+
* gate). Deliberately BROAD/lenient — a false "no" only SKIPS a destructive op, so
|
|
48
|
+
* erring toward "not yes" is safe. NEVER use this for the adult-consent gate: a
|
|
49
|
+
* false positive there would enable adult content without genuine consent. Use
|
|
50
|
+
* classifyConsentReply() for that. */
|
|
47
51
|
function isAffirmative(reply) {
|
|
48
52
|
if (typeof reply !== "string")
|
|
49
53
|
return false;
|
|
50
54
|
return /^(yes|allow|allowed|true|on|ok(ay)?|sure|agree|confirm|enable|i'?m? ?18|18\+?|adult)/i.test(reply.trim());
|
|
51
55
|
}
|
|
56
|
+
// The exact option labels the adult-consent card presents. Single source of truth
|
|
57
|
+
// shared by the card and its STRICT reply classifier so the gate matches on option
|
|
58
|
+
// IDENTITY, never on a loose heuristic.
|
|
59
|
+
const CONSENT_YES_LABEL = "Yes — I'm 18+ and it's legal in my region";
|
|
60
|
+
const CONSENT_NO_LABEL = "No — keep it SFW";
|
|
61
|
+
// STRICT, whole-string affirmatives/declines accepted from the card's free-text
|
|
62
|
+
// ("Other") box in addition to the exact option labels. Anchored ^…$ so ONLY an
|
|
63
|
+
// unambiguous token counts — unlike the broad isAffirmative() PREFIX match, a
|
|
64
|
+
// free-text reply like "adult content is illegal here", "On second thought, no",
|
|
65
|
+
// or "18 is the age but I decline" can NEVER be read as consent. Consent semantics
|
|
66
|
+
// (18+ AND legal) really live in the affirmative BUTTON; these bare tokens are the
|
|
67
|
+
// pragmatic fallback for a user who types instead of clicking.
|
|
68
|
+
const CONSENT_STRICT_YES_RE = /^(yes|y|yep|yeah|i agree|i consent|agreed?|confirm(ed)?|enable)$/i;
|
|
69
|
+
const CONSENT_STRICT_NO_RE = /^(no|n|nope|decline|declined|cancel|keep (it )?sfw|sfw)$/i;
|
|
70
|
+
/**
|
|
71
|
+
* STRICT classification of an adult-consent card reply — the ONLY gate that may
|
|
72
|
+
* turn adult mode ON. Returns:
|
|
73
|
+
* "grant" → the EXACT affirmative option, or a strict whole-string yes token
|
|
74
|
+
* → turn consent ON.
|
|
75
|
+
* "decline" → the EXACT decline option, or a strict whole-string no token
|
|
76
|
+
* → turn consent OFF.
|
|
77
|
+
* "unclear" → anything else (free text, ambiguous, non-string) → change NOTHING
|
|
78
|
+
* (never grants; never revokes a prior genuine grant).
|
|
79
|
+
* Never routes through isAffirmative() — a loose prefix match must not gate adult
|
|
80
|
+
* content.
|
|
81
|
+
*/
|
|
82
|
+
function classifyConsentReply(reply) {
|
|
83
|
+
if (typeof reply !== "string")
|
|
84
|
+
return "unclear";
|
|
85
|
+
// Exact OPTION IDENTITY: a button click echoes the card's label verbatim, so match
|
|
86
|
+
// it BYTE-FOR-BYTE — no .trim(). Trimming here would strip zero-width/BOM/line-
|
|
87
|
+
// separator characters (U+2028/U+2029/U+FEFF, \n) and let a near-label like
|
|
88
|
+
// " Yes — I'm 18+…" pass as the exact affirmative, defeating the exact-identity
|
|
89
|
+
// invariant this classifier exists to hold.
|
|
90
|
+
if (reply === CONSENT_YES_LABEL)
|
|
91
|
+
return "grant";
|
|
92
|
+
if (reply === CONSENT_NO_LABEL)
|
|
93
|
+
return "decline";
|
|
94
|
+
// Free-text ("Other" box) fallback: a small set of unambiguous whole-string tokens.
|
|
95
|
+
// This path is DELIBERATELY loose — a user who types " yes " is consenting — so a
|
|
96
|
+
// minimal .trim() of surrounding whitespace is applied ONLY here, never to the
|
|
97
|
+
// exact-label comparison above.
|
|
98
|
+
const t = reply.trim();
|
|
99
|
+
if (CONSENT_STRICT_YES_RE.test(t))
|
|
100
|
+
return "grant";
|
|
101
|
+
if (CONSENT_STRICT_NO_RE.test(t))
|
|
102
|
+
return "decline";
|
|
103
|
+
return "unclear";
|
|
104
|
+
}
|
|
52
105
|
function ok(value) {
|
|
53
106
|
return {
|
|
54
107
|
content: [
|
|
@@ -199,6 +252,10 @@ export const __panelToolsTestHooks = {
|
|
|
199
252
|
},
|
|
200
253
|
isRetrySafeCmd,
|
|
201
254
|
isTransientReconnectError,
|
|
255
|
+
// Adult-consent classifier + its exact card labels (#390 gate tests).
|
|
256
|
+
classifyConsentReply,
|
|
257
|
+
CONSENT_YES_LABEL,
|
|
258
|
+
CONSENT_NO_LABEL,
|
|
202
259
|
// #384 live-canvas capture fallback (defined later in the module).
|
|
203
260
|
reconstructUiFromState: (reply) => reconstructUiFromState(reply),
|
|
204
261
|
resolveWorkflowInput: (args, ctx, allowStateFallback = true) => resolveWorkflowInput(args, ctx, allowStateFallback),
|
|
@@ -2006,10 +2063,37 @@ function getAskTiming() {
|
|
|
2006
2063
|
}
|
|
2007
2064
|
/** True when an error is the bridge's reply-TIMEOUT for a card (the tab never
|
|
2008
2065
|
* replied within the window), NOT a genuine transport/command error. Only a
|
|
2009
|
-
* timeout warrants polling the late-reply buffer for a slow-but-valid answer.
|
|
2066
|
+
* timeout warrants polling the late-reply buffer for a slow-but-valid answer.
|
|
2067
|
+
*
|
|
2068
|
+
* Anchored (`^…$`) on the bridge's CANONICAL no-reply message — the WHOLE message
|
|
2069
|
+
* ui-bridge `dispatch()` emits: `Panel tab <id> did not reply to "<cmd>" within <N>
|
|
2070
|
+
* ms — the ComfyUI tab may be backgrounded or frozen`. Whole-message anchoring is
|
|
2071
|
+
* deliberate: a genuine transport error that merely WRAPS or embeds the phrase —
|
|
2072
|
+
* e.g. `relay failed: Panel tab abcd did not reply to "ask_user" within 500 ms;
|
|
2073
|
+
* reconnecting`, or `upstream service did not reply to us within 500 ms` — must NOT
|
|
2074
|
+
* be mis-handled as a recoverable card-reply timeout. It mirrors the same canonical
|
|
2075
|
+
* distinction the panel_open_workflow ack-timeout path draws. Crucial for the
|
|
2076
|
+
* consent gate: a real transport failure must reach fail(), never be quietly
|
|
2077
|
+
* reported as an unchanged-state success. */
|
|
2010
2078
|
function isReplyTimeoutError(err) {
|
|
2079
|
+
// AUTHORITATIVE: the bridge tags every reply-timeout with a typed marker
|
|
2080
|
+
// (markReplyTimeout). Keying on it makes the recoverable-timeout decision robust to
|
|
2081
|
+
// ANY tab_id — including one containing spaces, which the text form below can't
|
|
2082
|
+
// segment (ui-bridge accepts an arbitrary-string tab_id).
|
|
2083
|
+
if (isReplyTimeoutTagged(err))
|
|
2084
|
+
return true;
|
|
2085
|
+
// FALLBACK (test-injected plain errors / any untagged error): an EXACT whole-message
|
|
2086
|
+
// match of the bridge's canonical no-reply. Literal single space before "ms" (the
|
|
2087
|
+
// bridge always emits "within <N> ms"), no `.trim()`, `^` at true start (no /m flag)
|
|
2088
|
+
// and a HARD end-of-input `(?![\s\S])` — NOT `$`, which in JS also matches just
|
|
2089
|
+
// before a final "\n" and would let `canonical + "\n"` slip through. The tab-id
|
|
2090
|
+
// segment is `.+?` (lazy, bounded by the fixed ` did not reply to "` that follows —
|
|
2091
|
+
// an ≤8-char id slice can't contain that 19-char delimiter) so a spaced tab_id still
|
|
2092
|
+
// matches here too. So a prefixed/suffixed wrapper, whitespace/newline-wrapped text,
|
|
2093
|
+
// or noncanonical spacing ("within 500ms") does NOT match and is surfaced as a real
|
|
2094
|
+
// error. Case-sensitive: the message is a fixed literal template.
|
|
2011
2095
|
const msg = err instanceof Error ? err.message : String(err ?? "");
|
|
2012
|
-
return
|
|
2096
|
+
return /^Panel tab .+? did not reply to "[^"]*" within \d+ ms — the ComfyUI tab may be backgrounded or frozen(?![\s\S])/.test(msg);
|
|
2013
2097
|
}
|
|
2014
2098
|
/**
|
|
2015
2099
|
* Actionable error string when THIS session has no interactive surface able to
|
|
@@ -2042,6 +2126,109 @@ function askSurfaceError(ctx) {
|
|
|
2042
2126
|
"the user directly in plain chat text, or invoke panel_ask from an interactive " +
|
|
2043
2127
|
"ComfyUI browser tab (not nested inside an exec/headless call).");
|
|
2044
2128
|
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Route a canvas-requiring panel UI command (set_todo, open_civitai) onto the live
|
|
2131
|
+
* DESKTOP canvas tab when THIS session's bound tab is a headless (mobile/remote)
|
|
2132
|
+
* client (#624). The mobile / remote pseudo-panel accepts ONLY show_media and
|
|
2133
|
+
* rejects every other ServerCommand with the (client-authored) string "mobile
|
|
2134
|
+
* client has no open canvas" — a misleading, mobile-specific error even when a real
|
|
2135
|
+
* desktop ComfyUI canvas is open in the same session. These commands render on the
|
|
2136
|
+
* desktop panel surface (the footer TODO tray, the in-panel CivitAI browser), so
|
|
2137
|
+
* when the bound tab is canvas-less we resolve the SAME interactive desktop tab the
|
|
2138
|
+
* graph/workflow tools bind to instead of blasting the command at the headless
|
|
2139
|
+
* client. This is the desktop fall-through; genuine tab-mirror sessions are bound to
|
|
2140
|
+
* the desktop tab already (non-headless) and are therefore untouched.
|
|
2141
|
+
*
|
|
2142
|
+
* Returns:
|
|
2143
|
+
* - `{ tabId }` → REDIRECT the dispatch onto the interactive (canvas-owning)
|
|
2144
|
+
* desktop tab (the sole one, else the last-active one when it is itself a canvas);
|
|
2145
|
+
* - `{ error }` → the ONLY connected client is canvas-less, so fail with the REAL
|
|
2146
|
+
* reason (named honestly) rather than surfacing the raw mobile "no open canvas";
|
|
2147
|
+
* - `null` → the bound tab is already a reachable canvas, the choice among 2+
|
|
2148
|
+
* desktop tabs is ambiguous, or the bridge can't enumerate headlessness — in every
|
|
2149
|
+
* such case the normal `ctx.call` path (unchanged) runs.
|
|
2150
|
+
*/
|
|
2151
|
+
function desktopCanvasRedirect(ctx, label) {
|
|
2152
|
+
const b = ctx.bridge;
|
|
2153
|
+
// Older / lightweight bridges can't classify tabs — leave routing exactly as-is.
|
|
2154
|
+
if (typeof b.isHeadless !== "function" || typeof b.tabs !== "function")
|
|
2155
|
+
return null;
|
|
2156
|
+
// Only intervene when THIS session is bound to a canvas-less (mobile/remote) client.
|
|
2157
|
+
// A desktop-bound session — healthy, or merely orphaned by a reconnect — is left to
|
|
2158
|
+
// the normal ctx.call path and its reconnect/rebind machinery untouched.
|
|
2159
|
+
if (!b.isHeadless(ctx.tabId))
|
|
2160
|
+
return null;
|
|
2161
|
+
// Bound to a headless client: find the interactive (canvas-owning) DESKTOP tabs, the
|
|
2162
|
+
// SAME filter rebindToActiveTab/ensureReachable use for graph/workflow bindings.
|
|
2163
|
+
const live = b.tabs();
|
|
2164
|
+
const interactive = Array.isArray(live)
|
|
2165
|
+
? live.filter((t) => !b.isHeadless(t.tab_id))
|
|
2166
|
+
: [];
|
|
2167
|
+
if (interactive.length === 1)
|
|
2168
|
+
return { tabId: interactive[0].tab_id };
|
|
2169
|
+
if (interactive.length === 0) {
|
|
2170
|
+
return {
|
|
2171
|
+
error: `${label} needs an open ComfyUI desktop panel canvas to render, but this session ` +
|
|
2172
|
+
`is attached to a canvas-less client (a mobile/remote viewer or a headless run) ` +
|
|
2173
|
+
`and no desktop ComfyUI panel tab is connected. Open the ComfyUI browser tab with ` +
|
|
2174
|
+
`the comfyui-mcp-panel pack installed, then retry.`,
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
// 2+ interactive desktop tabs: prefer the last-active one when it is itself a canvas.
|
|
2178
|
+
// The bound tab is headless, so we must NOT fall through to ctx.call here — that would
|
|
2179
|
+
// dispatch right back at the canvas-less client and reproduce "mobile client has no
|
|
2180
|
+
// open canvas". When we can't single out one canvas tab, fail with the REAL reason.
|
|
2181
|
+
if (typeof b.resolveActiveTabId === "function") {
|
|
2182
|
+
try {
|
|
2183
|
+
const active = b.resolveActiveTabId();
|
|
2184
|
+
if (active && !b.isHeadless(active))
|
|
2185
|
+
return { tabId: active };
|
|
2186
|
+
}
|
|
2187
|
+
catch {
|
|
2188
|
+
/* ambiguous active-tab resolution — fall through to the ambiguity error below */
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
return {
|
|
2192
|
+
error: `${label} needs an open ComfyUI desktop panel canvas, but this session is bound to ` +
|
|
2193
|
+
`a canvas-less (mobile/remote) client and multiple desktop tabs are open — it can't ` +
|
|
2194
|
+
`pick one automatically. Switch to the ComfyUI tab you want, rebind with ` +
|
|
2195
|
+
`panel_set_workflow_target({mode:"current"}), then retry.`,
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
/** Dispatch a command to a SPECIFIC tab id (the #624 desktop-canvas redirect target)
|
|
2199
|
+
* and wrap the reply in the SAME ok()/fail() envelope ctx.call uses. The redirect
|
|
2200
|
+
* target came straight from a live `bridge.tabs()` enumeration, so it is already
|
|
2201
|
+
* reachable — this is a thin send that does NOT mutate ctx.tabId (the session's own
|
|
2202
|
+
* binding is left intact). For a retry-safe (idempotent full-replace) command it
|
|
2203
|
+
* mirrors ctx.call's single post-drop retry: on a transient reconnect error it settles
|
|
2204
|
+
* briefly, RE-RESOLVES the desktop tab (it may have reconnected under a new id), and
|
|
2205
|
+
* re-sends once before surfacing the error. `reResolve` returns the fresh desktop tab
|
|
2206
|
+
* id (or undefined to reuse `tabId`). */
|
|
2207
|
+
async function dispatchToTab(ctx, tabId, cmd, timeoutMs, reResolve) {
|
|
2208
|
+
try {
|
|
2209
|
+
return ok(await ctx.bridge.send(cmd, { tabId, timeoutMs }));
|
|
2210
|
+
}
|
|
2211
|
+
catch (err) {
|
|
2212
|
+
if (isRetrySafeCmd(cmd) && isTransientReconnectError(err)) {
|
|
2213
|
+
try {
|
|
2214
|
+
await sleep(retrySettleMs());
|
|
2215
|
+
const fresh = reResolve?.() ?? tabId;
|
|
2216
|
+
return ok(await ctx.bridge.send(cmd, { tabId: fresh, timeoutMs }));
|
|
2217
|
+
}
|
|
2218
|
+
catch (err2) {
|
|
2219
|
+
return fail(err2);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
return fail(err);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
/** Re-resolve the desktop redirect target after a transient drop — returns the fresh
|
|
2226
|
+
* interactive desktop tab id, or undefined when it can't be re-picked (so the caller
|
|
2227
|
+
* falls back to the original id for its single retry). */
|
|
2228
|
+
function reResolveDesktopTab(ctx, label) {
|
|
2229
|
+
const r = desktopCanvasRedirect(ctx, label);
|
|
2230
|
+
return r?.tabId;
|
|
2231
|
+
}
|
|
2045
2232
|
/** Poll the bridge's late-reply buffer for a validated ask answer that arrived
|
|
2046
2233
|
* after the card-reply timeout, up to the grace budget. undefined if none. */
|
|
2047
2234
|
async function pollLateAskReply(bridge, askId, timing) {
|
|
@@ -2802,16 +2989,64 @@ export function buildPanelToolDefs() {
|
|
|
2802
2989
|
// that lost its live binding (reconnect/reload/workflow-switch) throws a
|
|
2803
2990
|
// false `no connected tab` here even though graph tools just worked.
|
|
2804
2991
|
ctx.ensureReachable?.();
|
|
2805
|
-
|
|
2992
|
+
// #390: the enclosing MCP `tools/call` is killed at ~300s. A hardcoded 300s
|
|
2993
|
+
// card wait raced that budget with ZERO margin, so an idle user who never
|
|
2994
|
+
// clicked blew the whole tool call as a transport timeout ("timed out
|
|
2995
|
+
// awaiting tools/call after 300s") instead of resolving cleanly. CLAMP the
|
|
2996
|
+
// card deadline under the budget (the same getAskTiming() ceiling panel_ask
|
|
2997
|
+
// and confirm use) with a stable ask_id, and on a reply-timeout poll the
|
|
2998
|
+
// bridge's late-reply buffer for a bounded grace so a slow-but-VALID pick is
|
|
2999
|
+
// still honored. If the user is simply away, resolve cleanly with
|
|
3000
|
+
// { nsfw_allowed:false, timed_out:true } WITHOUT touching consent — a timeout
|
|
3001
|
+
// NEVER grants the gate; the persistent decision is unchanged and can be read
|
|
3002
|
+
// back with panel_get_content_mode, or the user re-asked, on the next turn.
|
|
3003
|
+
const timing = getAskTiming();
|
|
3004
|
+
const askId = randomUUID();
|
|
3005
|
+
const askCard = {
|
|
2806
3006
|
cmd: "ask_user",
|
|
3007
|
+
ask_id: askId,
|
|
2807
3008
|
question,
|
|
2808
3009
|
header: "18+ consent",
|
|
2809
3010
|
options: [
|
|
2810
|
-
{ label:
|
|
2811
|
-
{ label:
|
|
3011
|
+
{ label: CONSENT_YES_LABEL, description: "Enable adult content for this session" },
|
|
3012
|
+
{ label: CONSENT_NO_LABEL, description: "Stay in safe-for-work mode" },
|
|
2812
3013
|
],
|
|
2813
|
-
}
|
|
2814
|
-
|
|
3014
|
+
};
|
|
3015
|
+
let reply;
|
|
3016
|
+
let timedOut = false;
|
|
3017
|
+
try {
|
|
3018
|
+
reply = await ctx.bridge.send(askCard, { tabId: ctx.tabId, timeoutMs: timing.deadlineMs });
|
|
3019
|
+
}
|
|
3020
|
+
catch (err) {
|
|
3021
|
+
// Only a card-reply TIMEOUT is recoverable here: poll the late buffer for a
|
|
3022
|
+
// slow-but-valid answer. Any other error (no panel, transport failure)
|
|
3023
|
+
// propagates to the outer catch → fail(), exactly as before.
|
|
3024
|
+
if (!isReplyTimeoutError(err))
|
|
3025
|
+
throw err;
|
|
3026
|
+
timedOut = true;
|
|
3027
|
+
reply = await pollLateAskReply(ctx.bridge, askId, timing);
|
|
3028
|
+
}
|
|
3029
|
+
// STRICT gate: adult mode turns ON only on the EXACT affirmative option (or a
|
|
3030
|
+
// strict whole-string yes token) — never via the loose isAffirmative() prefix
|
|
3031
|
+
// match, so a free-text late reply like "adult content is illegal here" or "On
|
|
3032
|
+
// second thought, no" can't grant. An UNCLEAR reply (incl. a no-answer timeout,
|
|
3033
|
+
// where reply is undefined) changes NOTHING: it neither grants nor revokes a
|
|
3034
|
+
// prior genuine grant. Only an EXACT decline explicitly reverts to SFW.
|
|
3035
|
+
const decision = classifyConsentReply(reply);
|
|
3036
|
+
if (decision === "unclear") {
|
|
3037
|
+
const current = getNsfwConsent();
|
|
3038
|
+
return ok({
|
|
3039
|
+
nsfw_allowed: current.allowed,
|
|
3040
|
+
decided_at: current.decidedAt ?? null,
|
|
3041
|
+
...(timedOut ? { timed_out: true } : {}),
|
|
3042
|
+
note: timedOut
|
|
3043
|
+
? "The 18+ consent card wasn't answered in time, so nothing changed — adult content stays gated by the existing state. " +
|
|
3044
|
+
"Ask again when the user is back, or read the current state with panel_get_content_mode."
|
|
3045
|
+
: "The 18+ consent card wasn't answered with a clear yes/no, so nothing changed — the existing content-mode state is unchanged. " +
|
|
3046
|
+
"Ask again with a clear choice, or read the current state with panel_get_content_mode.",
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
3049
|
+
const allowed = decision === "grant";
|
|
2815
3050
|
const state = setNsfwConsent(allowed);
|
|
2816
3051
|
return ok({
|
|
2817
3052
|
nsfw_allowed: state.allowed,
|
|
@@ -2849,7 +3084,19 @@ export function buildPanelToolDefs() {
|
|
|
2849
3084
|
// momentarily backgrounded. set_todo is a non-destructive, idempotent full-
|
|
2850
3085
|
// replace UI write (already in RETRY_SAFE_CMDS), so give it the same sane 15s
|
|
2851
3086
|
// bound as the other UI-state writes (workflow_save) instead of a tight 5s.
|
|
2852
|
-
async (args, ctx) =>
|
|
3087
|
+
async (args, ctx) => {
|
|
3088
|
+
// #624: the footer TODO tray is a DESKTOP panel surface. When this session's
|
|
3089
|
+
// bound tab is a headless (mobile/remote) client, resolve the live desktop
|
|
3090
|
+
// canvas tab instead of dispatching at the headless client — which would
|
|
3091
|
+
// reject with the misleading "mobile client has no open canvas".
|
|
3092
|
+
const redirect = desktopCanvasRedirect(ctx, "panel_set_todo");
|
|
3093
|
+
if (redirect?.error)
|
|
3094
|
+
return fail(redirect.error);
|
|
3095
|
+
if (redirect?.tabId) {
|
|
3096
|
+
return dispatchToTab(ctx, redirect.tabId, { cmd: "set_todo", items: args.items }, 15000, () => reResolveDesktopTab(ctx, "panel_set_todo"));
|
|
3097
|
+
}
|
|
3098
|
+
return ctx.call({ cmd: "set_todo", items: args.items }, 15000);
|
|
3099
|
+
}),
|
|
2853
3100
|
def("panel_open_civitai", "Open the in-panel CivitAI browser for the user, pre-seeded with a search term and suggested filters, so they can VISUALLY browse and pick a model / LoRA / checkpoint / workflow / image. When the user asks about — or you're recommending — specific CivitAI models/LoRAs/checkpoints (e.g. 'what's a good relight LoRA?'), PREFER opening this docked browser and highlighting your picks over a text-only answer: it docks beside the chat (dock defaults true) so chat and results stay visible together, and it lets the user SEE the actual cards instead of reading a table. Typical show-don't-tell flow: panel_open_civitai (docked) → panel_civitai_search to refine → panel_civitai_results to READ the metadata + URLs → panel_civitai_highlight the one(s) you recommend, with a brief text summary of why. Set a helpful query + filters matched to their goal (including the browsing level). Their selection comes back to you as a normal chat message — UNLESS the panel is muted, in which case they download it directly themselves. Prefer this over guessing a specific model or asking them to paste a URL.", {
|
|
2854
3101
|
query: z
|
|
2855
3102
|
.string()
|
|
@@ -2888,14 +3135,25 @@ export function buildPanelToolDefs() {
|
|
|
2888
3135
|
const query = creator
|
|
2889
3136
|
? `@${creator}${rawQuery ? " " + rawQuery : " "}`
|
|
2890
3137
|
: args.query;
|
|
2891
|
-
|
|
3138
|
+
const cmd = {
|
|
2892
3139
|
cmd: "open_civitai",
|
|
2893
3140
|
query,
|
|
2894
3141
|
tab: args.tab,
|
|
2895
3142
|
browsingLevels,
|
|
2896
3143
|
filters: args.filters,
|
|
2897
3144
|
dock: args.dock,
|
|
2898
|
-
}
|
|
3145
|
+
};
|
|
3146
|
+
// #624: the in-panel CivitAI browser is a DESKTOP panel surface. When this
|
|
3147
|
+
// session's bound tab is a headless (mobile/remote) client, resolve the live
|
|
3148
|
+
// desktop canvas tab instead of dispatching at the headless client — which
|
|
3149
|
+
// would reject with the misleading "mobile client has no open canvas".
|
|
3150
|
+
const redirect = desktopCanvasRedirect(ctx, "panel_open_civitai");
|
|
3151
|
+
if (redirect?.error)
|
|
3152
|
+
return fail(redirect.error);
|
|
3153
|
+
if (redirect?.tabId) {
|
|
3154
|
+
return await dispatchToTab(ctx, redirect.tabId, cmd, 10000, () => reResolveDesktopTab(ctx, "panel_open_civitai"));
|
|
3155
|
+
}
|
|
3156
|
+
return await ctx.call(cmd, 10000);
|
|
2899
3157
|
}
|
|
2900
3158
|
catch (err) {
|
|
2901
3159
|
return fail(err);
|
|
@@ -3133,7 +3391,9 @@ export function buildPanelToolDefs() {
|
|
|
3133
3391
|
deferredBind = true;
|
|
3134
3392
|
rebindNote =
|
|
3135
3393
|
" No panel tab is connected yet — cleared the stale binding; this session will " +
|
|
3136
|
-
"follow (bind onto) the tab as soon as one reconnects. Retry your graph tool in a
|
|
3394
|
+
"follow (bind onto) the tab as soon as one reconnects. Retry your graph tool in a " +
|
|
3395
|
+
"moment; if nothing reconnects shortly, ask the user to refresh (reload) the ComfyUI " +
|
|
3396
|
+
"browser tab, which reconnects the Agent panel after a restart (not an install issue).";
|
|
3137
3397
|
}
|
|
3138
3398
|
// Detect the rebind regardless of whether awaitReachable or rebindToActiveTab
|
|
3139
3399
|
// performed it (either mutates ctx.tabId), so the note is never swallowed.
|
|
@@ -3167,7 +3427,7 @@ export function buildPanelToolDefs() {
|
|
|
3167
3427
|
return ok({ ...target, ...(deferredBind ? { deferred: true } : {}), note: hint + rebindNote });
|
|
3168
3428
|
}),
|
|
3169
3429
|
def("panel_new_workflow", "Open a brand-new BLANK workflow in a NEW TAB. Use this whenever the user wants a 'new workflow' / 'fresh canvas' / 'start over for a new project'. This does NOT touch their current workflow — it opens a separate tab. NEVER use panel_clear for a new workflow (panel_clear wipes the CURRENT graph and is only for 'clear/reset this canvas').", {}, async (_args, ctx) => ctx.call({ cmd: "workflow_new" }, 15000)),
|
|
3170
|
-
def("panel_open_workflow", "Open / switch to a workflow by path or filename (from panel_list_workflows). Switches the active tab to it.", { path: z.string().describe("Workflow path, filename, or key from panel_list_workflows.") },
|
|
3430
|
+
def("panel_open_workflow", "Open / switch to a workflow by path or filename (from panel_list_workflows). Switches the active tab to it. If the workflow was ALREADY open and its .json changed on disk out-of-band, the result carries stale:true (or stale:\"unknown\" when staleness couldn't be verified) with a stale_hint — the canvas still shows the version this tab loaded (it is NOT auto-reloaded); call panel_load_workflow to load the on-disk version.", { path: z.string().describe("Workflow path, filename, or key from panel_list_workflows.") },
|
|
3171
3431
|
// Verify-after-timeout (#215/#319/#496): a backgrounded/frozen or already-open
|
|
3172
3432
|
// tab can be slow to ack workflow_open even though the switch succeeded. On an
|
|
3173
3433
|
// ack-timeout, confirm via the authoritative workflow_list active identity
|