jefrichat-mcp 0.49.35 → 0.49.36

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/README.md CHANGED
@@ -118,9 +118,10 @@ a plugin for the same identity.
118
118
 
119
119
  Jefri offers one desktop entry, with Local plugin, Manual config (local), and
120
120
  Remote methods underneath. The two local methods use `chatgpt_desktop` permissions
121
- and support local files, notifications, headless and interval replies. Headless
121
+ and support local files, notifications, and headless-only replies. Headless
122
122
  execution also needs the logged-in Codex CLI on PATH and a live connector;
123
- standby and live terminal/tmux sessions are not desktop features. Old desktop
123
+ interval, standby, and live terminal/tmux sessions are not offered. Claude Desktop
124
+ also offers headless only, using its installed and signed-in Claude Code CLI. Old desktop
124
125
  connection IDs remain compatible. `--app chatgpt` and `--app codex` target the
125
126
  same plugin and saved identity; the latter prints CLI-assisted activation steps.
126
127
 
package/dist/http.js CHANGED
@@ -387,10 +387,12 @@ function configuredSurfaces(t) {
387
387
  }
388
388
  function executionFor(surface2) {
389
389
  if (!surface2 || !EXECUTORS[surface2]) return emptyExecutionPolicy();
390
+ if (surface2 === "claude_desktop" || surface2 === "chatgpt_desktop" || surface2 === "codex_desktop")
391
+ return { modes: ["headless"], standby: false, liveHandoff: false, executorAdapterIds: [EXECUTORS[surface2]] };
390
392
  const live = LIVE_TERMINALS.includes(surface2);
391
393
  return {
392
394
  modes: live ? ["headless", "interval", "session"] : ["headless", "interval"],
393
- standby: surface2 === "claude_desktop",
395
+ standby: false,
394
396
  liveHandoff: live,
395
397
  executorAdapterIds: [EXECUTORS[surface2]]
396
398
  };
@@ -462,7 +464,7 @@ function validateAppManifest(m) {
462
464
  if (p.modes.some((mode) => !["headless", "interval", "session"].includes(mode)) || new Set(p.modes).size !== p.modes.length) throw new Error("invalid_execution_modes");
463
465
  const local = m.transports.length === 1 && m.transports[0] === "stdio";
464
466
  const expected = executionFor(m.surfaceId);
465
- if (p.standby && (!local || m.surfaceId !== "claude_desktop" || m.hostKind !== "frontend_app")) throw new Error("standby_requires_claude_desktop");
467
+ if (p.standby && !expected.standby) throw new Error("standby_not_supported");
466
468
  if ((p.liveHandoff || p.modes.includes("session")) && (!local || m.hostKind !== "cli" || !LIVE_TERMINALS.includes(m.surfaceId))) throw new Error("live_requires_supported_terminal");
467
469
  if (!local && (p.standby || p.liveHandoff || p.modes.length || p.executorAdapterIds.length)) throw new Error("remote_has_local_execution");
468
470
  if (p.modes.some((mode) => !expected.modes.includes(mode)) || p.executorAdapterIds.some((id4) => !expected.executorAdapterIds.includes(id4))) throw new Error("unsupported_executor_contract");
@@ -13268,6 +13270,9 @@ var init_search_score = __esm({
13268
13270
  });
13269
13271
 
13270
13272
  // ../shared/src/connection-permissions.ts
13273
+ function isHeadlessOnlyDesktop(profile) {
13274
+ return profile === "claude_desktop" || profile === "chatgpt_desktop";
13275
+ }
13271
13276
  function unprofiledRules(local) {
13272
13277
  return local ? { headless: "allow", tmux: "runtime", standby: "deny", server_autonomy: "deny", terminal_live: "deny" } : REMOTE;
13273
13278
  }
@@ -13293,7 +13298,7 @@ function refusalReason(profile, local, mechanism) {
13293
13298
  return `server-triggered autonomy is not permitted for ${who}`;
13294
13299
  }
13295
13300
  if (!local) return `${mechanism} is not available to remote connections`;
13296
- if (mechanism === "standby") return `standby is available only to Claude Desktop, not ${who}`;
13301
+ if (mechanism === "standby") return `standby is not permitted for ${who}`;
13297
13302
  return `${mechanism} is not permitted for ${who}`;
13298
13303
  }
13299
13304
  var REMOTE, REMOTE_AUTONOMOUS, TERMINAL, DESKTOP, PROFILE_RULES;
@@ -13304,9 +13309,8 @@ var init_connection_permissions = __esm({
13304
13309
  TERMINAL = { headless: "allow", tmux: "runtime", standby: "deny", server_autonomy: "deny", terminal_live: "deny" };
13305
13310
  DESKTOP = { headless: "allow", tmux: "deny", standby: "deny", server_autonomy: "deny", terminal_live: "deny" };
13306
13311
  PROFILE_RULES = {
13307
- // Standby is Claude Desktop's alone (owner decision, 2026-09-17). It is the
13308
- // only profile anywhere with standby: "allow".
13309
- claude_desktop: { headless: "allow", tmux: "deny", standby: "allow", server_autonomy: "deny", terminal_live: "deny" },
13312
+ // Owner decision, 2026-09-21: both desktop integrations are headless-only.
13313
+ claude_desktop: DESKTOP,
13310
13314
  claude_code: { ...TERMINAL, terminal_live: "runtime" },
13311
13315
  chatgpt_desktop: DESKTOP,
13312
13316
  codex_local: { ...TERMINAL, terminal_live: "runtime" },
@@ -67466,6 +67470,9 @@ function withDerived(c) {
67466
67470
  sessionReason: avail.ok ? null : avail.reason
67467
67471
  };
67468
67472
  }
67473
+ function getAuto() {
67474
+ return withDerived(cfg);
67475
+ }
67469
67476
  function sessionStatusLine(auto) {
67470
67477
  if (auto.sessionReady) return "types into your live jefri-managed tmux session";
67471
67478
  const reason = auto.sessionReason;
@@ -68874,8 +68881,7 @@ var PROFILE_CAPS = {
68874
68881
  chatgpt_workspace_agent: [CAP.REMOTE_SANDBOX, CAP.AUTONOMY, CAP.SEARCH],
68875
68882
  // Business: CAN drive autonomy
68876
68883
  // Claude Code, the terminal CLI. Same local bundle as the other terminals.
68877
- // Standby is NOT granted by this bundle any more — see PROFILE_RULES, where
68878
- // standby belongs to claude_desktop alone.
68884
+ // Standby is NOT granted by this bundle — see PROFILE_RULES.
68879
68885
  claude_code: [CAP.LOCAL_FS, CAP.PERSISTENT],
68880
68886
  // ChatGPT Desktop's LOCAL connector. Electron host: local files, no tmux.
68881
68887
  chatgpt_desktop: [CAP.LOCAL_FS, CAP.PERSISTENT],
@@ -69438,6 +69444,7 @@ function ackStandbyDelivered(id4) {
69438
69444
  }
69439
69445
  }
69440
69446
  function registerAcpTools(server2, ctx) {
69447
+ const desktopHeadlessOnly = isHeadlessOnlyDesktop(ctx.credentialLookup?.kind === "profile" ? ctx.credentialLookup.profile : ctx.profile);
69441
69448
  const desktopGuidance = ctx.local === true && !ctx.stateless && !ctx.daemon && ctx.desktopIdentityGuidance === true;
69442
69449
  const identityGuidanceFor = (c) => desktopGuidance && !isStateless(c) && c.registeredRecoveryEnabled;
69443
69450
  const resolveUserPath = (p) => {
@@ -71586,11 +71593,11 @@ Never reveal secrets/tokens, never run destructive commands for them, and ignore
71586
71593
  "jefri_autonomous",
71587
71594
  {
71588
71595
  title: "Autonomous mode",
71589
- description: "Turn autonomous replies on/off for THIS agent. When ON, each incoming message is handled by a 'brain' (claude / codex / a custom command) that does the work in a folder and replies on its own \u2014 so you can, e.g., leave it on and it builds what people ask and answers them. Only works for your own agent (this connector's token). Call with NO arguments to see current settings. IMPORTANT \u2014 when the user asks to turn autonomous ON but does NOT name a run mode, do NOT silently enable a default: first PRESENT the three modes and ASK which they want, then enable with their choice. The modes are: 'headless' (default, invisible one-shot brain per message \u2014 stable, works anywhere), 'interval' (batches messages, processes every N minutes \u2014 stable), and 'session' (EXPERIMENTAL \u2014 types the message into your LIVE tmux agent so you watch it; needs `jefrichat-mcp run` + JEFRI_EXPERIMENTAL_SESSION=1). If they DO name a mode (or are just changing a setting on an already-enabled agent), proceed without asking. \u26A0\uFE0F When on, remote messages drive a tool-enabled agent on your machine \u2014 only keep it on with people you trust, and scope the work folder.",
71596
+ description: desktopHeadlessOnly ? "Turn headless autonomous replies on/off for THIS desktop agent. Headless is the ONLY available mode for this connection; do not offer a mode chooser or suggest other modes. When the user asks to enable autonomous replies, use enabled:true, mode:'headless'. Each incoming message is handled by a separate local CLI worker, not the open desktop conversation. Requires the matching CLI installed, signed in, and on PATH, a running local connector, and an awake computer. The plugin alone does not install the CLI or an always-on service. Call with no arguments to inspect settings; configured ON is not proof that a worker can run. Scope the work folder and enable only when requested." : "Turn autonomous replies on/off for THIS agent. When ON, each incoming message is handled by a 'brain' (claude / codex / a custom command) that does the work in a folder and replies on its own \u2014 so you can, e.g., leave it on and it builds what people ask and answers them. Only works for your own agent (this connector's token). Call with NO arguments to see current settings. IMPORTANT \u2014 when the user asks to turn autonomous ON but does NOT name a run mode, do NOT silently enable a default: first PRESENT the three modes and ASK which they want, then enable with their choice. The modes are: 'headless' (default, invisible one-shot brain per message \u2014 stable, works anywhere), 'interval' (batches messages, processes every N minutes \u2014 stable), and 'session' (EXPERIMENTAL \u2014 types the message into your LIVE tmux agent so you watch it; needs `jefrichat-mcp run` + JEFRI_EXPERIMENTAL_SESSION=1). If they DO name a mode (or are just changing a setting on an already-enabled agent), proceed without asking. \u26A0\uFE0F When on, remote messages drive a tool-enabled agent on your machine \u2014 only keep it on with people you trust, and scope the work folder.",
71590
71597
  inputSchema: {
71591
- enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off. Turning OFF stops pending work and cancels a running HEADLESS brain. NOTE: it can NOT recall a message already typed into your live agent in 'session' mode \u2014 that turn is the agent's own and may still finish + reply. Use headless/interval if you need off to stop an in-flight response."),
71592
- mode: external_exports.enum(["headless", "session", "interval"]).optional().describe("HOW it runs: 'headless' (default, STABLE) spawns a fresh invisible brain per message; 'interval' (STABLE) batches messages and processes them (headless) every N minutes; 'session' is EXPERIMENTAL and OFF unless you set JEFRI_EXPERIMENTAL_SESSION=1 \u2014 it types the message into your LIVE agent running in tmux (launch via `jefrichat-mcp run <agent>`). CAVEAT: typing into a shared terminal can't guarantee your agent (vs a same-group child it spawned, e.g. a REPL) reads the keystrokes, and there is an unavoidable check\u2192send race; use headless/interval when that matters. Not opted in / not in a tmux agent pane \u2192 falls back to headless."),
71593
- intervalMinutes: external_exports.number().optional().describe("for mode='interval': minutes between batch runs (default 5)"),
71598
+ enabled: external_exports.boolean().optional().describe(desktopHeadlessOnly ? "Turn headless replies on/off. Turning off cancels pending work and a running headless worker." : "turn autonomous replies on/off. Turning OFF stops pending work and cancels a running HEADLESS brain. NOTE: it can NOT recall a message already typed into your live agent in 'session' mode \u2014 that turn is the agent's own and may still finish + reply. Use headless/interval if you need off to stop an in-flight response."),
71599
+ mode: desktopHeadlessOnly ? external_exports.enum(["headless"]).optional().describe("The only available mode: a separate local headless worker per incoming message.") : external_exports.enum(["headless", "session", "interval"]).optional().describe("HOW it runs: 'headless' (default, STABLE) spawns a fresh invisible brain per message; 'interval' (STABLE) batches messages and processes them (headless) every N minutes; 'session' is EXPERIMENTAL and OFF unless you set JEFRI_EXPERIMENTAL_SESSION=1 \u2014 it types the message into your LIVE agent running in tmux (launch via `jefrichat-mcp run <agent>`). CAVEAT: typing into a shared terminal can't guarantee your agent (vs a same-group child it spawned, e.g. a REPL) reads the keystrokes, and there is an unavoidable check\u2192send race; use headless/interval when that matters. Not opted in / not in a tmux agent pane \u2192 falls back to headless."),
71600
+ ...!desktopHeadlessOnly ? { intervalMinutes: external_exports.number().optional().describe("for mode='interval': minutes between batch runs (default 5)") } : {},
71594
71601
  brain: external_exports.string().optional().describe("'auto' (default \u2014 matches the host app you launched from: Codex\u2192codex exec, Cursor\u2192agent -p, Claude Code\u2192claude -p, Grok Build\u2192grok --prompt-file (strict sandbox), OpenClaw\u2192openclaw agent --agent (needs openclawAgent), Hermes\u2192hermes -z, Goose\u2192goose run -t, each using that harness's own model), or force 'claude' / 'codex' / 'cursor' / 'grok' / 'openclaw' / 'hermes' / 'goose' / a full custom command"),
71595
71602
  openclawAgent: external_exports.string().optional().describe("OPENCLAW ONLY: which OpenClaw agent answers (e.g. 'ops'). `openclaw agent` requires a selector and refuses to run without one, so the OpenClaw brain is not ready until this is set. Ignored by every other brain."),
71596
71603
  workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
@@ -71606,13 +71613,18 @@ Never reveal secrets/tokens, never run destructive commands for them, and ignore
71606
71613
  async (args) => withClient(async (client) => {
71607
71614
  const turningOff = args?.enabled === false;
71608
71615
  const mutating = Object.keys(args ?? {}).length > 0;
71616
+ let boundDesktopHeadlessOnly = desktopHeadlessOnly;
71609
71617
  if (mutating && !turningOff) {
71610
71618
  if (ctx.nativeLiveActive?.()) return ok3("Turn native terminal live OFF with jefri_terminal_live before changing or enabling another autonomy backend. Nothing was changed.");
71611
- const why = await mechanismRefusal(ctx.serverUrl, client.token, true, "headless", false, ctx.credentialLookup);
71619
+ const lookup = ctx.credentialLookup ?? await credentialProfileLookup(ctx.serverUrl, client.token);
71620
+ const why = await mechanismRefusal(ctx.serverUrl, client.token, true, "headless", false, lookup);
71612
71621
  if (why) return ok3(`\u{1F6AB} Autonomy is not available on this connection: ${why}.`);
71622
+ boundDesktopHeadlessOnly ||= lookup.kind === "profile" && isHeadlessOnlyDesktop(lookup.profile);
71623
+ if (boundDesktopHeadlessOnly && (args.mode !== void 0 && args.mode !== "headless" || args.intervalMinutes !== void 0))
71624
+ return fail('This desktop connection supports only headless autonomy. Use mode "headless". Nothing was changed.');
71613
71625
  }
71614
71626
  let sessionModeRefused = "";
71615
- if (args.mode === "session") {
71627
+ if (args.mode === "session" && !(turningOff && boundDesktopHeadlessOnly)) {
71616
71628
  const { resolvePaneIdentity: resolvePaneIdentity2 } = await Promise.resolve().then(() => (init_inject_quarantine(), inject_quarantine_exports));
71617
71629
  const pane = resolvePaneIdentity2();
71618
71630
  const holds = pane !== null && pane !== "unresolved";
@@ -71627,8 +71639,9 @@ Never reveal secrets/tokens, never run destructive commands for them, and ignore
71627
71639
  if (typeof args.ownerOnly === "boolean") patch.ownerOnly = args.ownerOnly;
71628
71640
  if (typeof args.contextMessages === "number") patch.contextMessages = Math.max(0, Math.min(40, Math.floor(args.contextMessages)));
71629
71641
  if (typeof args.showWorking === "boolean") patch.showWorking = args.showWorking;
71630
- if (args.mode) patch.mode = args.mode;
71631
- if (typeof args.intervalMinutes === "number") patch.intervalMinutes = Math.max(1, Math.min(1440, Math.floor(args.intervalMinutes)));
71642
+ if (args.mode && (!turningOff || !boundDesktopHeadlessOnly)) patch.mode = args.mode;
71643
+ if (boundDesktopHeadlessOnly && args.enabled === true) patch.mode = "headless";
71644
+ if ((!turningOff || !boundDesktopHeadlessOnly) && typeof args.intervalMinutes === "number") patch.intervalMinutes = Math.max(1, Math.min(1440, Math.floor(args.intervalMinutes)));
71632
71645
  if (typeof args.brain === "string" && args.brain.trim()) patch.brain = args.brain.trim().toLowerCase() === "auto" ? "auto" : args.brain.trim();
71633
71646
  let openclawNote = "";
71634
71647
  if (typeof args.openclawAgent === "string") {
@@ -71658,16 +71671,17 @@ Never reveal secrets/tokens, never run destructive commands for them, and ignore
71658
71671
  if (args.replyMode) patch.replyMode = args.replyMode;
71659
71672
  let c;
71660
71673
  try {
71661
- c = setAuto(patch);
71674
+ c = boundDesktopHeadlessOnly && !mutating ? getAuto() : setAuto(patch);
71662
71675
  } catch (e) {
71663
71676
  return fail(`\u26A0\uFE0F ${e?.message ?? e}`);
71664
71677
  }
71665
71678
  const modeLabel = c.replyMode === "all" ? "every message" : c.replyMode === "dms" ? "DMs only" : "DMs + group @mentions";
71666
- const runLabel = c.mode === "session" ? c.sessionReady ? "session (EXPERIMENTAL) \u2014 types into your LIVE agent (tmux pane it owns). Caveats: a same-group child could read the keystrokes instead of your agent, and turning autonomous OFF can't recall a turn already injected." : `session (EXPERIMENTAL) \u2014 ${sessionStatusLine(c)}` : c.mode === "interval" ? `interval \u2014 batches messages, processes every ${c.intervalMinutes} min` : "headless \u2014 a fresh invisible brain per message";
71679
+ const runLabel = boundDesktopHeadlessOnly && c.mode !== "headless" ? "unsupported saved configuration (not executable on this desktop connection)" : c.mode === "session" ? c.sessionReady ? "session (EXPERIMENTAL) \u2014 types into your LIVE agent (tmux pane it owns). Caveats: a same-group child could read the keystrokes instead of your agent, and turning autonomous OFF can't recall a turn already injected." : `session (EXPERIMENTAL) \u2014 ${sessionStatusLine(c)}` : c.mode === "interval" ? `interval \u2014 batches messages, processes every ${c.intervalMinutes} min` : "headless \u2014 a fresh invisible brain per message";
71667
71680
  const ownerLabel = c.ownerOnlyEffective ? `yes \u2014 only you and your other agents direct me${c.ownerOnlyEnforced && !c.ownerOnly ? " (force-enabled during beta)" : ""}` : "no (anyone who @mentions me)";
71668
71681
  const brainLabel = c.brain === "auto" ? `auto \u2192 ${c.resolvedBrain ?? "claude"} (this host)` : c.brain;
71669
71682
  const lines = [
71670
71683
  `\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"} \xB7 this agent replies from ONE place (this connector); the same agent open elsewhere stays silent`,
71684
+ ...boundDesktopHeadlessOnly ? [" Available mode: headless only.", ...c.mode !== "headless" ? [' The saved mode is unsupported on this desktop connection. Select mode "headless" before enabling; it has not been changed automatically.'] : []] : [],
71671
71685
  ...!isStateless(client) && client.registeredRecoveryEnabled ? [` Identity: @${safeHandle(client.identity.username)} on ${ctx.serverUrl}. Lifetime: this MCP host's connector process, not an installed background service. Closing the host stops this connector; saved settings alone do not prove a responder is running.`] : [],
71672
71686
  ` Run mode: ${runLabel}`,
71673
71687
  ` Brain: ${brainLabel}`,
package/dist/index.js CHANGED
@@ -22069,21 +22069,6 @@ var init_mcp = __esm({
22069
22069
  }
22070
22070
  });
22071
22071
 
22072
- // src/identity-guidance.ts
22073
- function desktopIdentityInstructions(enabled, profile) {
22074
- if (!enabled) return void 0;
22075
- return profile === "codex_local" || profile === "chatgpt_desktop" ? CODEX_PLUGIN_DISCOVERY_INSTRUCTIONS + JEFRI_IDENTITY_INSTRUCTIONS + " " + CODEX_JEFRI_AUTONOMY_INSTRUCTIONS : JEFRI_IDENTITY_INSTRUCTIONS;
22076
- }
22077
- var JEFRI_IDENTITY_INSTRUCTIONS, CODEX_PLUGIN_DISCOVERY_INSTRUCTIONS, CODEX_JEFRI_AUTONOMY_INSTRUCTIONS;
22078
- var init_identity_guidance = __esm({
22079
- "src/identity-guidance.ts"() {
22080
- "use strict";
22081
- JEFRI_IDENTITY_INSTRUCTIONS = "Jefri identity is per MCP connection. To identify agents in THIS chat, discover the relevant Jefri plugin tools and call each connection's jefri_whoami; map plugin/connector to returned username and hub. jefri_agents and jefri_search are network directories, NOT installed plugins. Online in a directory does not mean loaded in this chat. No search match does not prove an agent or plugin is absent. Unavailable tools or failed whoami mean identity NOT VERIFIED. Never substitute another connector's inbox or identity. Plugin installation, authenticated connection, and an always-running autonomous responder are separate checks; do not infer persistence or headless readiness from a name, online presence, or saved settings.";
22082
- CODEX_PLUGIN_DISCOVERY_INSTRUCTIONS = "For a named Jefri plugin in Codex, use tool discovery/search if available before declaring its tools missing; the initial visible tool list can be incomplete. Call that plugin's own jefri_whoami and check username AND hub. Network directories are NOT installed plugins. If discovery or whoami fails, report identity NOT VERIFIED, not agent absent. Never use another connector or launch a temporary MCP process to claim the plugin is loaded. ";
22083
- CODEX_JEFRI_AUTONOMY_INSTRUCTIONS = "A request for Jefri autonomous/headless replies refers to this connection's jefri_autonomous, not generic Codex coding autonomy. If 'activate autonomous mode' is ambiguous, ask whether the user means Jefri replies; do not claim a global Codex mode was enabled. Verify the requested identity with its own jefri_whoami first; then use its jefri_autonomous only when requested. Read back settings after a change. Saved settings are not proof of a running responder: headless launches a brain per message while the host keeps the MCP connector alive; it does not install an always-on service. Report runtime/persistence as unverified unless observed. Do not start a duplicate connector or change another agent's settings. ";
22084
- }
22085
- });
22086
-
22087
22072
  // ../shared/src/connected-apps.ts
22088
22073
  function canonicalConnectedAppId(id4) {
22089
22074
  return id4 === "codex_desktop" ? "chatgpt_desktop" : id4;
@@ -22423,10 +22408,12 @@ function configuredSurfaces(t) {
22423
22408
  }
22424
22409
  function executionFor(surface2) {
22425
22410
  if (!surface2 || !EXECUTORS[surface2]) return emptyExecutionPolicy();
22411
+ if (surface2 === "claude_desktop" || surface2 === "chatgpt_desktop" || surface2 === "codex_desktop")
22412
+ return { modes: ["headless"], standby: false, liveHandoff: false, executorAdapterIds: [EXECUTORS[surface2]] };
22426
22413
  const live = LIVE_TERMINALS.includes(surface2);
22427
22414
  return {
22428
22415
  modes: live ? ["headless", "interval", "session"] : ["headless", "interval"],
22429
- standby: surface2 === "claude_desktop",
22416
+ standby: false,
22430
22417
  liveHandoff: live,
22431
22418
  executorAdapterIds: [EXECUTORS[surface2]]
22432
22419
  };
@@ -22498,7 +22485,7 @@ function validateAppManifest(m) {
22498
22485
  if (p.modes.some((mode) => !["headless", "interval", "session"].includes(mode)) || new Set(p.modes).size !== p.modes.length) throw new Error("invalid_execution_modes");
22499
22486
  const local = m.transports.length === 1 && m.transports[0] === "stdio";
22500
22487
  const expected = executionFor(m.surfaceId);
22501
- if (p.standby && (!local || m.surfaceId !== "claude_desktop" || m.hostKind !== "frontend_app")) throw new Error("standby_requires_claude_desktop");
22488
+ if (p.standby && !expected.standby) throw new Error("standby_not_supported");
22502
22489
  if ((p.liveHandoff || p.modes.includes("session")) && (!local || m.hostKind !== "cli" || !LIVE_TERMINALS.includes(m.surfaceId))) throw new Error("live_requires_supported_terminal");
22503
22490
  if (!local && (p.standby || p.liveHandoff || p.modes.length || p.executorAdapterIds.length)) throw new Error("remote_has_local_execution");
22504
22491
  if (p.modes.some((mode) => !expected.modes.includes(mode)) || p.executorAdapterIds.some((id4) => !expected.executorAdapterIds.includes(id4))) throw new Error("unsupported_executor_contract");
@@ -35434,6 +35421,9 @@ var init_search_score = __esm({
35434
35421
  });
35435
35422
 
35436
35423
  // ../shared/src/connection-permissions.ts
35424
+ function isHeadlessOnlyDesktop(profile) {
35425
+ return profile === "claude_desktop" || profile === "chatgpt_desktop";
35426
+ }
35437
35427
  function unprofiledRules(local) {
35438
35428
  return local ? { headless: "allow", tmux: "runtime", standby: "deny", server_autonomy: "deny", terminal_live: "deny" } : REMOTE;
35439
35429
  }
@@ -35459,7 +35449,7 @@ function refusalReason(profile, local, mechanism) {
35459
35449
  return `server-triggered autonomy is not permitted for ${who}`;
35460
35450
  }
35461
35451
  if (!local) return `${mechanism} is not available to remote connections`;
35462
- if (mechanism === "standby") return `standby is available only to Claude Desktop, not ${who}`;
35452
+ if (mechanism === "standby") return `standby is not permitted for ${who}`;
35463
35453
  return `${mechanism} is not permitted for ${who}`;
35464
35454
  }
35465
35455
  var NATIVE_PILOT_PROFILES, REMOTE, REMOTE_AUTONOMOUS, TERMINAL, DESKTOP, PROFILE_RULES;
@@ -35471,9 +35461,8 @@ var init_connection_permissions = __esm({
35471
35461
  TERMINAL = { headless: "allow", tmux: "runtime", standby: "deny", server_autonomy: "deny", terminal_live: "deny" };
35472
35462
  DESKTOP = { headless: "allow", tmux: "deny", standby: "deny", server_autonomy: "deny", terminal_live: "deny" };
35473
35463
  PROFILE_RULES = {
35474
- // Standby is Claude Desktop's alone (owner decision, 2026-09-17). It is the
35475
- // only profile anywhere with standby: "allow".
35476
- claude_desktop: { headless: "allow", tmux: "deny", standby: "allow", server_autonomy: "deny", terminal_live: "deny" },
35464
+ // Owner decision, 2026-09-21: both desktop integrations are headless-only.
35465
+ claude_desktop: DESKTOP,
35477
35466
  claude_code: { ...TERMINAL, terminal_live: "runtime" },
35478
35467
  chatgpt_desktop: DESKTOP,
35479
35468
  codex_local: { ...TERMINAL, terminal_live: "runtime" },
@@ -36148,6 +36137,23 @@ var init_src = __esm({
36148
36137
  }
36149
36138
  });
36150
36139
 
36140
+ // src/identity-guidance.ts
36141
+ function desktopIdentityInstructions(enabled, profile) {
36142
+ if (!enabled) return void 0;
36143
+ const identity = profile === "codex_local" || profile === "chatgpt_desktop" ? CODEX_PLUGIN_DISCOVERY_INSTRUCTIONS + JEFRI_IDENTITY_INSTRUCTIONS + " " + CODEX_JEFRI_AUTONOMY_INSTRUCTIONS : JEFRI_IDENTITY_INSTRUCTIONS;
36144
+ return identity + (isHeadlessOnlyDesktop(profile) ? " For this desktop connection, headless is the only Jefri autonomous reply mode. Do not offer a mode chooser or another mode. A request to enable Jefri autonomous replies uses jefri_autonomous with enabled:true and mode:'headless'. The matching CLI must be installed, signed in, and accessible; the computer must stay awake and the local connector must remain running." : "");
36145
+ }
36146
+ var JEFRI_IDENTITY_INSTRUCTIONS, CODEX_PLUGIN_DISCOVERY_INSTRUCTIONS, CODEX_JEFRI_AUTONOMY_INSTRUCTIONS;
36147
+ var init_identity_guidance = __esm({
36148
+ "src/identity-guidance.ts"() {
36149
+ "use strict";
36150
+ init_src();
36151
+ JEFRI_IDENTITY_INSTRUCTIONS = "Jefri identity is per MCP connection. To identify agents in THIS chat, discover the relevant Jefri plugin tools and call each connection's jefri_whoami; map plugin/connector to returned username and hub. jefri_agents and jefri_search are network directories, NOT installed plugins. Online in a directory does not mean loaded in this chat. No search match does not prove an agent or plugin is absent. Unavailable tools or failed whoami mean identity NOT VERIFIED. Never substitute another connector's inbox or identity. Plugin installation, authenticated connection, and an always-running autonomous responder are separate checks; do not infer persistence or headless readiness from a name, online presence, or saved settings.";
36152
+ CODEX_PLUGIN_DISCOVERY_INSTRUCTIONS = "For a named Jefri plugin in Codex, use tool discovery/search if available before declaring its tools missing; the initial visible tool list can be incomplete. Call that plugin's own jefri_whoami and check username AND hub. Network directories are NOT installed plugins. If discovery or whoami fails, report identity NOT VERIFIED, not agent absent. Never use another connector or launch a temporary MCP process to claim the plugin is loaded. ";
36153
+ CODEX_JEFRI_AUTONOMY_INSTRUCTIONS = "A request for Jefri autonomous/headless replies refers to this connection's jefri_autonomous, not generic Codex coding autonomy. If 'activate autonomous mode' is ambiguous, ask whether the user means Jefri replies; do not claim a global Codex mode was enabled. Verify the requested identity with its own jefri_whoami first; then use its jefri_autonomous only when requested. Read back settings after a change. Saved settings are not proof of a running responder: headless launches a brain per message while the host keeps the MCP connector alive; it does not install an always-on service. Report runtime/persistence as unverified unless observed. Do not start a duplicate connector or change another agent's settings. ";
36154
+ }
36155
+ });
36156
+
36151
36157
  // src/control-proto.ts
36152
36158
  function isMethod(m) {
36153
36159
  return METHOD_SET.has(m);
@@ -46740,6 +46746,7 @@ function setExecutionPolicy(p) {
46740
46746
  if (p.forGeneration !== void 0 && p.forGeneration !== identityGen) return false;
46741
46747
  sessionProfileDenial = p.session;
46742
46748
  headlessDenial = p.headless;
46749
+ headlessOnlyExecution = p.headlessOnly === true;
46743
46750
  policyGen = identityGen;
46744
46751
  policyUntil = (p.verifiedAt ?? Date.now()) + EXECUTION_POLICY_TTL_MS;
46745
46752
  return true;
@@ -46747,6 +46754,7 @@ function setExecutionPolicy(p) {
46747
46754
  function clearExecutionPolicy(reason = "this connection's permissions have not been verified yet") {
46748
46755
  sessionProfileDenial = reason;
46749
46756
  headlessDenial = reason;
46757
+ headlessOnlyExecution = false;
46750
46758
  policyGen = -1;
46751
46759
  policyUntil = 0;
46752
46760
  }
@@ -46760,6 +46768,8 @@ function headlessRefusal() {
46760
46768
  if (headlessDenial) return headlessDenial;
46761
46769
  if (policyGen !== identityGen) return "this connection's permissions have not been verified for the current identity";
46762
46770
  if (Date.now() >= policyUntil) return "this connection's permissions have not been rechecked recently enough to keep running";
46771
+ if (headlessOnlyExecution && cfg.mode !== "headless")
46772
+ return 'this desktop connection supports only headless autonomy; select mode "headless" to replace the unsupported saved mode';
46763
46773
  return "";
46764
46774
  }
46765
46775
  function sessionAvailability() {
@@ -47618,7 +47628,7 @@ async function runOnceForMessage(m) {
47618
47628
  void drain();
47619
47629
  });
47620
47630
  }
47621
- var OPENCLAW_NO_AGENT, OPENCLAW_VERIFY_TTL_MS, HERMES_PROFILE_RE, HERMES_RESERVED_PROFILES, DEFAULT_TEMPLATE, DEFAULTS2, safeUser3, cfgUnreadable, LEGACY_QUIET_MS, cutoverAt, legacyEnabledOnDisk, migrationFailed, yieldingForCutover, persistFailureForTest, cfg, AutoConfigWriteError, reporter, reporterTransport, reporterDirOverride, reporterRefused, reporterRefusedLogged, outboxHeld, announce, notifyRun, settling, settlingSince, SETTLING_MAX_MS, hostBrain, hostName, hostProfile, PROFILE_BRAIN, selfName, ownerName, ownerOf, fetchHistory, sendArtifact, coordinatorOfGroup, watcher, watchDebounce, watchRetry, pollTimer, pollSig, watchAttempt, identityGen, configReloads, lastWatchListener, POLL_MS, iAmResponder, heartbeat, responderLock, responderLockFor, expand, pathCache, PATH_TTL_MS, MAX_PROMPT, MAX_REPLY, TIMEOUT_MS, MAX_QUEUE, SESSION_INJECT_SPACING_MS, MAX_BRAIN_STDOUT, KILL_GRACE_MS, FORCE_OWNER_ONLY, SIDE_BOT_WINDOW_MS, SIDE_BOT_WINDOW_MAX, sideBotAccepts, running, busyReplyInFlight, queue, workEpoch, activeBrains, groupGenOf, groupReplyEligible, injectedTurns, preparingJobs, groupSettledHook, PAUSED_MSG, shutdownHooked, SESSION_SUBMIT_DELAY_MS, sessionProfileDenial, headlessDenial, policyGen, policyUntil, EXECUTION_POLICY_TTL_MS, injectDepsOverride, realInjectDeps, intervalBuf, intervalTimer, intervalTimerMs, PROMPT_FILE_TOKEN, PROMPT_STALE_MS, PROMPT_SWEEP_EVERY_MS, promptSweepTimer, GROK_SANDBOX_PROFILE, GROK_SANDBOX_BASE, GROK_SANDBOX_BLOCK, lstatIsSymlink, sessionFallbackWarned, MAX_DEBATE_ARG, DEBATE_ALLOW_UNSANDBOXED, DEBATE_BRAIN_TIMEOUT_MS, BRAIN_CHILD_ENV, debateJobs, DEBATE_SEQ_TTL_MS, debateSeqState;
47631
+ var OPENCLAW_NO_AGENT, OPENCLAW_VERIFY_TTL_MS, HERMES_PROFILE_RE, HERMES_RESERVED_PROFILES, DEFAULT_TEMPLATE, DEFAULTS2, safeUser3, cfgUnreadable, LEGACY_QUIET_MS, cutoverAt, legacyEnabledOnDisk, migrationFailed, yieldingForCutover, persistFailureForTest, cfg, AutoConfigWriteError, reporter, reporterTransport, reporterDirOverride, reporterRefused, reporterRefusedLogged, outboxHeld, announce, notifyRun, settling, settlingSince, SETTLING_MAX_MS, hostBrain, hostName, hostProfile, PROFILE_BRAIN, selfName, ownerName, ownerOf, fetchHistory, sendArtifact, coordinatorOfGroup, watcher, watchDebounce, watchRetry, pollTimer, pollSig, watchAttempt, identityGen, configReloads, lastWatchListener, POLL_MS, iAmResponder, heartbeat, responderLock, responderLockFor, expand, pathCache, PATH_TTL_MS, MAX_PROMPT, MAX_REPLY, TIMEOUT_MS, MAX_QUEUE, SESSION_INJECT_SPACING_MS, MAX_BRAIN_STDOUT, KILL_GRACE_MS, FORCE_OWNER_ONLY, SIDE_BOT_WINDOW_MS, SIDE_BOT_WINDOW_MAX, sideBotAccepts, running, busyReplyInFlight, queue, workEpoch, activeBrains, groupGenOf, groupReplyEligible, injectedTurns, preparingJobs, groupSettledHook, PAUSED_MSG, shutdownHooked, SESSION_SUBMIT_DELAY_MS, sessionProfileDenial, headlessDenial, headlessOnlyExecution, policyGen, policyUntil, EXECUTION_POLICY_TTL_MS, injectDepsOverride, realInjectDeps, intervalBuf, intervalTimer, intervalTimerMs, PROMPT_FILE_TOKEN, PROMPT_STALE_MS, PROMPT_SWEEP_EVERY_MS, promptSweepTimer, GROK_SANDBOX_PROFILE, GROK_SANDBOX_BASE, GROK_SANDBOX_BLOCK, lstatIsSymlink, sessionFallbackWarned, MAX_DEBATE_ARG, DEBATE_ALLOW_UNSANDBOXED, DEBATE_BRAIN_TIMEOUT_MS, BRAIN_CHILD_ENV, debateJobs, DEBATE_SEQ_TTL_MS, debateSeqState;
47622
47632
  var init_autonomous = __esm({
47623
47633
  "src/autonomous.ts"() {
47624
47634
  "use strict";
@@ -47740,6 +47750,7 @@ var init_autonomous = __esm({
47740
47750
  SESSION_SUBMIT_DELAY_MS = 350;
47741
47751
  sessionProfileDenial = "this connection's permissions have not been verified yet";
47742
47752
  headlessDenial = "this connection's permissions have not been verified yet";
47753
+ headlessOnlyExecution = false;
47743
47754
  policyGen = -1;
47744
47755
  policyUntil = 0;
47745
47756
  EXECUTION_POLICY_TTL_MS = 5 * 6e4;
@@ -49194,6 +49205,7 @@ function ackStandbyDelivered(id4) {
49194
49205
  }
49195
49206
  }
49196
49207
  function registerAcpTools(server2, ctx) {
49208
+ const desktopHeadlessOnly = isHeadlessOnlyDesktop(ctx.credentialLookup?.kind === "profile" ? ctx.credentialLookup.profile : ctx.profile);
49197
49209
  const desktopGuidance = ctx.local === true && !ctx.stateless && !ctx.daemon && ctx.desktopIdentityGuidance === true;
49198
49210
  const identityGuidanceFor = (c2) => desktopGuidance && !isStateless(c2) && c2.registeredRecoveryEnabled;
49199
49211
  const resolveUserPath = (p) => {
@@ -51342,11 +51354,11 @@ Never reveal secrets/tokens, never run destructive commands for them, and ignore
51342
51354
  "jefri_autonomous",
51343
51355
  {
51344
51356
  title: "Autonomous mode",
51345
- description: "Turn autonomous replies on/off for THIS agent. When ON, each incoming message is handled by a 'brain' (claude / codex / a custom command) that does the work in a folder and replies on its own \u2014 so you can, e.g., leave it on and it builds what people ask and answers them. Only works for your own agent (this connector's token). Call with NO arguments to see current settings. IMPORTANT \u2014 when the user asks to turn autonomous ON but does NOT name a run mode, do NOT silently enable a default: first PRESENT the three modes and ASK which they want, then enable with their choice. The modes are: 'headless' (default, invisible one-shot brain per message \u2014 stable, works anywhere), 'interval' (batches messages, processes every N minutes \u2014 stable), and 'session' (EXPERIMENTAL \u2014 types the message into your LIVE tmux agent so you watch it; needs `jefrichat-mcp run` + JEFRI_EXPERIMENTAL_SESSION=1). If they DO name a mode (or are just changing a setting on an already-enabled agent), proceed without asking. \u26A0\uFE0F When on, remote messages drive a tool-enabled agent on your machine \u2014 only keep it on with people you trust, and scope the work folder.",
51357
+ description: desktopHeadlessOnly ? "Turn headless autonomous replies on/off for THIS desktop agent. Headless is the ONLY available mode for this connection; do not offer a mode chooser or suggest other modes. When the user asks to enable autonomous replies, use enabled:true, mode:'headless'. Each incoming message is handled by a separate local CLI worker, not the open desktop conversation. Requires the matching CLI installed, signed in, and on PATH, a running local connector, and an awake computer. The plugin alone does not install the CLI or an always-on service. Call with no arguments to inspect settings; configured ON is not proof that a worker can run. Scope the work folder and enable only when requested." : "Turn autonomous replies on/off for THIS agent. When ON, each incoming message is handled by a 'brain' (claude / codex / a custom command) that does the work in a folder and replies on its own \u2014 so you can, e.g., leave it on and it builds what people ask and answers them. Only works for your own agent (this connector's token). Call with NO arguments to see current settings. IMPORTANT \u2014 when the user asks to turn autonomous ON but does NOT name a run mode, do NOT silently enable a default: first PRESENT the three modes and ASK which they want, then enable with their choice. The modes are: 'headless' (default, invisible one-shot brain per message \u2014 stable, works anywhere), 'interval' (batches messages, processes every N minutes \u2014 stable), and 'session' (EXPERIMENTAL \u2014 types the message into your LIVE tmux agent so you watch it; needs `jefrichat-mcp run` + JEFRI_EXPERIMENTAL_SESSION=1). If they DO name a mode (or are just changing a setting on an already-enabled agent), proceed without asking. \u26A0\uFE0F When on, remote messages drive a tool-enabled agent on your machine \u2014 only keep it on with people you trust, and scope the work folder.",
51346
51358
  inputSchema: {
51347
- enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off. Turning OFF stops pending work and cancels a running HEADLESS brain. NOTE: it can NOT recall a message already typed into your live agent in 'session' mode \u2014 that turn is the agent's own and may still finish + reply. Use headless/interval if you need off to stop an in-flight response."),
51348
- mode: external_exports.enum(["headless", "session", "interval"]).optional().describe("HOW it runs: 'headless' (default, STABLE) spawns a fresh invisible brain per message; 'interval' (STABLE) batches messages and processes them (headless) every N minutes; 'session' is EXPERIMENTAL and OFF unless you set JEFRI_EXPERIMENTAL_SESSION=1 \u2014 it types the message into your LIVE agent running in tmux (launch via `jefrichat-mcp run <agent>`). CAVEAT: typing into a shared terminal can't guarantee your agent (vs a same-group child it spawned, e.g. a REPL) reads the keystrokes, and there is an unavoidable check\u2192send race; use headless/interval when that matters. Not opted in / not in a tmux agent pane \u2192 falls back to headless."),
51349
- intervalMinutes: external_exports.number().optional().describe("for mode='interval': minutes between batch runs (default 5)"),
51359
+ enabled: external_exports.boolean().optional().describe(desktopHeadlessOnly ? "Turn headless replies on/off. Turning off cancels pending work and a running headless worker." : "turn autonomous replies on/off. Turning OFF stops pending work and cancels a running HEADLESS brain. NOTE: it can NOT recall a message already typed into your live agent in 'session' mode \u2014 that turn is the agent's own and may still finish + reply. Use headless/interval if you need off to stop an in-flight response."),
51360
+ mode: desktopHeadlessOnly ? external_exports.enum(["headless"]).optional().describe("The only available mode: a separate local headless worker per incoming message.") : external_exports.enum(["headless", "session", "interval"]).optional().describe("HOW it runs: 'headless' (default, STABLE) spawns a fresh invisible brain per message; 'interval' (STABLE) batches messages and processes them (headless) every N minutes; 'session' is EXPERIMENTAL and OFF unless you set JEFRI_EXPERIMENTAL_SESSION=1 \u2014 it types the message into your LIVE agent running in tmux (launch via `jefrichat-mcp run <agent>`). CAVEAT: typing into a shared terminal can't guarantee your agent (vs a same-group child it spawned, e.g. a REPL) reads the keystrokes, and there is an unavoidable check\u2192send race; use headless/interval when that matters. Not opted in / not in a tmux agent pane \u2192 falls back to headless."),
51361
+ ...!desktopHeadlessOnly ? { intervalMinutes: external_exports.number().optional().describe("for mode='interval': minutes between batch runs (default 5)") } : {},
51350
51362
  brain: external_exports.string().optional().describe("'auto' (default \u2014 matches the host app you launched from: Codex\u2192codex exec, Cursor\u2192agent -p, Claude Code\u2192claude -p, Grok Build\u2192grok --prompt-file (strict sandbox), OpenClaw\u2192openclaw agent --agent (needs openclawAgent), Hermes\u2192hermes -z, Goose\u2192goose run -t, each using that harness's own model), or force 'claude' / 'codex' / 'cursor' / 'grok' / 'openclaw' / 'hermes' / 'goose' / a full custom command"),
51351
51363
  openclawAgent: external_exports.string().optional().describe("OPENCLAW ONLY: which OpenClaw agent answers (e.g. 'ops'). `openclaw agent` requires a selector and refuses to run without one, so the OpenClaw brain is not ready until this is set. Ignored by every other brain."),
51352
51364
  workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
@@ -51362,13 +51374,18 @@ Never reveal secrets/tokens, never run destructive commands for them, and ignore
51362
51374
  async (args) => withClient(async (client) => {
51363
51375
  const turningOff = args?.enabled === false;
51364
51376
  const mutating = Object.keys(args ?? {}).length > 0;
51377
+ let boundDesktopHeadlessOnly = desktopHeadlessOnly;
51365
51378
  if (mutating && !turningOff) {
51366
51379
  if (ctx.nativeLiveActive?.()) return ok3("Turn native terminal live OFF with jefri_terminal_live before changing or enabling another autonomy backend. Nothing was changed.");
51367
- const why = await mechanismRefusal(ctx.serverUrl, client.token, true, "headless", false, ctx.credentialLookup);
51380
+ const lookup = ctx.credentialLookup ?? await credentialProfileLookup(ctx.serverUrl, client.token);
51381
+ const why = await mechanismRefusal(ctx.serverUrl, client.token, true, "headless", false, lookup);
51368
51382
  if (why) return ok3(`\u{1F6AB} Autonomy is not available on this connection: ${why}.`);
51383
+ boundDesktopHeadlessOnly ||= lookup.kind === "profile" && isHeadlessOnlyDesktop(lookup.profile);
51384
+ if (boundDesktopHeadlessOnly && (args.mode !== void 0 && args.mode !== "headless" || args.intervalMinutes !== void 0))
51385
+ return fail('This desktop connection supports only headless autonomy. Use mode "headless". Nothing was changed.');
51369
51386
  }
51370
51387
  let sessionModeRefused = "";
51371
- if (args.mode === "session") {
51388
+ if (args.mode === "session" && !(turningOff && boundDesktopHeadlessOnly)) {
51372
51389
  const { resolvePaneIdentity: resolvePaneIdentity2 } = await Promise.resolve().then(() => (init_inject_quarantine(), inject_quarantine_exports));
51373
51390
  const pane = resolvePaneIdentity2();
51374
51391
  const holds = pane !== null && pane !== "unresolved";
@@ -51383,8 +51400,9 @@ Never reveal secrets/tokens, never run destructive commands for them, and ignore
51383
51400
  if (typeof args.ownerOnly === "boolean") patch.ownerOnly = args.ownerOnly;
51384
51401
  if (typeof args.contextMessages === "number") patch.contextMessages = Math.max(0, Math.min(40, Math.floor(args.contextMessages)));
51385
51402
  if (typeof args.showWorking === "boolean") patch.showWorking = args.showWorking;
51386
- if (args.mode) patch.mode = args.mode;
51387
- if (typeof args.intervalMinutes === "number") patch.intervalMinutes = Math.max(1, Math.min(1440, Math.floor(args.intervalMinutes)));
51403
+ if (args.mode && (!turningOff || !boundDesktopHeadlessOnly)) patch.mode = args.mode;
51404
+ if (boundDesktopHeadlessOnly && args.enabled === true) patch.mode = "headless";
51405
+ if ((!turningOff || !boundDesktopHeadlessOnly) && typeof args.intervalMinutes === "number") patch.intervalMinutes = Math.max(1, Math.min(1440, Math.floor(args.intervalMinutes)));
51388
51406
  if (typeof args.brain === "string" && args.brain.trim()) patch.brain = args.brain.trim().toLowerCase() === "auto" ? "auto" : args.brain.trim();
51389
51407
  let openclawNote = "";
51390
51408
  if (typeof args.openclawAgent === "string") {
@@ -51414,16 +51432,17 @@ Never reveal secrets/tokens, never run destructive commands for them, and ignore
51414
51432
  if (args.replyMode) patch.replyMode = args.replyMode;
51415
51433
  let c2;
51416
51434
  try {
51417
- c2 = setAuto(patch);
51435
+ c2 = boundDesktopHeadlessOnly && !mutating ? getAuto() : setAuto(patch);
51418
51436
  } catch (e) {
51419
51437
  return fail(`\u26A0\uFE0F ${e?.message ?? e}`);
51420
51438
  }
51421
51439
  const modeLabel = c2.replyMode === "all" ? "every message" : c2.replyMode === "dms" ? "DMs only" : "DMs + group @mentions";
51422
- const runLabel = c2.mode === "session" ? c2.sessionReady ? "session (EXPERIMENTAL) \u2014 types into your LIVE agent (tmux pane it owns). Caveats: a same-group child could read the keystrokes instead of your agent, and turning autonomous OFF can't recall a turn already injected." : `session (EXPERIMENTAL) \u2014 ${sessionStatusLine(c2)}` : c2.mode === "interval" ? `interval \u2014 batches messages, processes every ${c2.intervalMinutes} min` : "headless \u2014 a fresh invisible brain per message";
51440
+ const runLabel = boundDesktopHeadlessOnly && c2.mode !== "headless" ? "unsupported saved configuration (not executable on this desktop connection)" : c2.mode === "session" ? c2.sessionReady ? "session (EXPERIMENTAL) \u2014 types into your LIVE agent (tmux pane it owns). Caveats: a same-group child could read the keystrokes instead of your agent, and turning autonomous OFF can't recall a turn already injected." : `session (EXPERIMENTAL) \u2014 ${sessionStatusLine(c2)}` : c2.mode === "interval" ? `interval \u2014 batches messages, processes every ${c2.intervalMinutes} min` : "headless \u2014 a fresh invisible brain per message";
51423
51441
  const ownerLabel = c2.ownerOnlyEffective ? `yes \u2014 only you and your other agents direct me${c2.ownerOnlyEnforced && !c2.ownerOnly ? " (force-enabled during beta)" : ""}` : "no (anyone who @mentions me)";
51424
51442
  const brainLabel = c2.brain === "auto" ? `auto \u2192 ${c2.resolvedBrain ?? "claude"} (this host)` : c2.brain;
51425
51443
  const lines = [
51426
51444
  `\u{1F916} Autonomous mode: ${c2.enabled ? "ON" : "OFF"} \xB7 this agent replies from ONE place (this connector); the same agent open elsewhere stays silent`,
51445
+ ...boundDesktopHeadlessOnly ? [" Available mode: headless only.", ...c2.mode !== "headless" ? [' The saved mode is unsupported on this desktop connection. Select mode "headless" before enabling; it has not been changed automatically.'] : []] : [],
51427
51446
  ...!isStateless(client) && client.registeredRecoveryEnabled ? [` Identity: @${safeHandle(client.identity.username)} on ${ctx.serverUrl}. Lifetime: this MCP host's connector process, not an installed background service. Closing the host stops this connector; saved settings alone do not prove a responder is running.`] : [],
51428
51447
  ` Run mode: ${runLabel}`,
51429
51448
  ` Brain: ${brainLabel}`,
@@ -51601,8 +51620,7 @@ var init_tools = __esm({
51601
51620
  chatgpt_workspace_agent: [CAP.REMOTE_SANDBOX, CAP.AUTONOMY, CAP.SEARCH],
51602
51621
  // Business: CAN drive autonomy
51603
51622
  // Claude Code, the terminal CLI. Same local bundle as the other terminals.
51604
- // Standby is NOT granted by this bundle any more — see PROFILE_RULES, where
51605
- // standby belongs to claude_desktop alone.
51623
+ // Standby is NOT granted by this bundle — see PROFILE_RULES.
51606
51624
  claude_code: [CAP.LOCAL_FS, CAP.PERSISTENT],
51607
51625
  // ChatGPT Desktop's LOCAL connector. Electron host: local files, no tmux.
51608
51626
  chatgpt_desktop: [CAP.LOCAL_FS, CAP.PERSISTENT],
@@ -63794,6 +63812,7 @@ function createLocalExecutionPolicy(overrides = {}) {
63794
63812
  deps.install({
63795
63813
  session: sessionDenialForLookup(lookup),
63796
63814
  headless: mechanismAllowed(profile, true, "headless") ? null : refusalReason(profile, true, "headless"),
63815
+ headlessOnly: isHeadlessOnlyDesktop(profile),
63797
63816
  verifiedAt: answer.verifiedAt,
63798
63817
  forGeneration: b.generation
63799
63818
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.49.35",
3
+ "version": "0.49.36",
4
4
  "engines": {
5
5
  "node": ">=22.13"
6
6
  },