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 +3 -2
- package/dist/http.js +32 -18
- package/dist/index.js +53 -34
- package/package.json +1 -1
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,
|
|
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
|
|
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:
|
|
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 &&
|
|
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
|
|
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
|
-
//
|
|
13308
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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:
|
|
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 &&
|
|
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
|
|
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
|
-
//
|
|
35475
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|
});
|