mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
feat: drop the typed first-message hint from the Discord bridge (#741)
Owner decision. Typing the note into a brand-new non-Claude tab spent a model turn per tab and, for a spawned worker or a scheduled run, landed with the first real prompt. Claude keeps its hint through the system prompt, and every other CLI is told nothing. The setting goes with the path it gated: sessionIdle, the fresh set, isHintableSession and the session:idle wiring. sendInput loses `fromUser`, which was only ever false for the hint, and the deps test pins that a reply is a person's own input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7c8d440928
commit
9d04a4fb02
14 files changed
+51
-189
No files matched your search
@@ -3319,13 +3319,6 @@
|
||||
</div>
|
||||
<label class="switch switch-sm"><input type="checkbox" id="discordStartupHint"><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="set-row">
|
||||
<div class="set-row-text">
|
||||
<span class="set-row-label">Also tell other CLIs, as a first message</span>
|
||||
<span class="set-row-desc">Sends the same note once as a message to a brand-new Codex, OpenCode or similar tab when it first becomes idle. Costs one model turn per tab, and for a spawned worker or a scheduled run it arrives with the first real prompt. Never sent to shell, restored, remote or Docker tabs. Off by default.</span>
|
||||
</div>
|
||||
<label class="switch switch-sm"><input type="checkbox" id="discordTypedHint"><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="set-row has-field">
|
||||
<div class="set-row-text">
|
||||
<span class="set-row-label">Which events</span>
|
||||
|
||||
@@ -1771,7 +1771,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
document.getElementById('discordPingMode').value = d.pingMode;
|
||||
document.getElementById('discordScope').value = d.scope;
|
||||
document.getElementById('discordStartupHint').checked = d.startupHint !== false;
|
||||
document.getElementById('discordTypedHint').checked = d.typedHint === true;
|
||||
const clearBtn = document.getElementById('discordClearBtn');
|
||||
if (clearBtn) clearBtn.style.display = d.hasToken ? '' : 'none';
|
||||
const gw = { off: 'Not connected (disabled).', connecting: 'Connecting to Discord…', connected: 'Connected: replies are being read.', error: `Not connected: ${d.gatewayError || 'error'}` };
|
||||
@@ -1797,7 +1796,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
pingMode: document.getElementById('discordPingMode').value,
|
||||
scope: document.getElementById('discordScope').value,
|
||||
startupHint: document.getElementById('discordStartupHint').checked,
|
||||
typedHint: document.getElementById('discordTypedHint').checked,
|
||||
};
|
||||
const token = document.getElementById('discordToken').value.trim();
|
||||
if (token) payload.token = token; // blank = keep the saved one (Remove token is the way to clear it)
|
||||
|
||||
@@ -2268,7 +2268,6 @@ export const DiscordUpdateSchema = z
|
||||
pingMode: z.enum(DISCORD_PING_MODES).optional(),
|
||||
scope: z.enum(WEBHOOK_SCOPES).optional(),
|
||||
startupHint: z.boolean().optional(),
|
||||
typedHint: z.boolean().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
+9
-32
@@ -520,13 +520,12 @@ export class WebServer extends EventEmitter {
|
||||
private discordBridge = new DiscordBridge({
|
||||
configDir: getDataDir(),
|
||||
fetchImpl: webviewFetch,
|
||||
sendInput: async (sessionId, text, fromUser) => {
|
||||
sendInput: async (sessionId, text) => {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return false;
|
||||
// fromUser is true only for a Discord reply (a person typed it); the startup hint is Codeman's own
|
||||
// text and must not auto-name the tab or count as the person's input.
|
||||
if (await session.writeViaMux(text, { fromUser }).catch(() => false)) return true;
|
||||
return session.write(text, { fromUser });
|
||||
// A Discord reply is a person's own input, so it names the tab like any other prompt.
|
||||
if (await session.writeViaMux(text, { fromUser: true }).catch(() => false)) return true;
|
||||
return session.write(text, { fromUser: true });
|
||||
},
|
||||
agentExited: (id) => !!this.sessions.get(id)?.paneExit,
|
||||
awaitingChoice: (id) => approvalInbox.listPending().some((item) => item.sessionId === id && item.kind !== 'idle'),
|
||||
@@ -2786,41 +2785,19 @@ export class WebServer extends EventEmitter {
|
||||
this.bridgeSessionEvent(event, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the startup hint may be typed into this tab: an external CLI (never Claude, which has it as a
|
||||
* launch flag, and never a shell), running locally. A remote (ssh) or Docker tab has no `codeman` on its
|
||||
* PATH, so the hint would tell the agent to run a command it does not have.
|
||||
*/
|
||||
private isHintableSession(sessionId: string): boolean {
|
||||
const session = this.sessions.get(sessionId);
|
||||
return !!session && isExternalCliMode(session.mode) && !session.remote && !session.docker;
|
||||
}
|
||||
|
||||
/** Keep the Discord thread in step with the tab: created, renamed, deleted. Never throws. */
|
||||
private bridgeSessionEvent(event: string, data: unknown): void {
|
||||
// Deletion is NOT handled here: `session:deleted` also fires for a detach (killMux=false), where the
|
||||
// session keeps running and can be re-attached. The real delete calls the bridge from the cleanup path.
|
||||
if (event !== SseEvent.SessionCreated && event !== SseEvent.SessionUpdated && event !== SseEvent.SessionIdle) {
|
||||
return;
|
||||
}
|
||||
const d = (data ?? {}) as {
|
||||
id?: string;
|
||||
name?: string;
|
||||
createdAt?: number;
|
||||
session?: { id?: string; name?: string; createdAt?: number };
|
||||
};
|
||||
if (event !== SseEvent.SessionCreated && event !== SseEvent.SessionUpdated) return;
|
||||
const d = (data ?? {}) as { id?: string; name?: string; session?: { id?: string; name?: string } };
|
||||
const s = d.session ?? d;
|
||||
if (!s.id) return;
|
||||
const id = s.id;
|
||||
const run =
|
||||
event === SseEvent.SessionIdle
|
||||
? // Claude is told at launch; a shell, a remote or a Docker tab must never be typed at.
|
||||
this.discordBridge.sessionIdle(id, this.isHintableSession(id))
|
||||
: event === SseEvent.SessionCreated
|
||||
? this.discordBridge
|
||||
.sessionCreated(id, s.name, s.createdAt)
|
||||
.then(() => this.discordBridge.sessionRenamed(id, s.name))
|
||||
: this.discordBridge.sessionRenamed(id, s.name);
|
||||
event === SseEvent.SessionCreated
|
||||
? this.discordBridge.sessionCreated(id, s.name).then(() => this.discordBridge.sessionRenamed(id, s.name))
|
||||
: this.discordBridge.sessionRenamed(id, s.name);
|
||||
void run.catch(() => undefined);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user