feat(discord): make the typed first-message hint a separate opt-in, off by default

Claude keeps the silent system-prompt hint (startupHint, on). Other CLIs are sent the typed message only with typedHint, which costs a model turn per tab.
This commit is contained in:
Devvyn committed 2026-10-30 01:38:37 +08:00
1 parent e95d38a944
commit 6d4357502b
10 files changed
+77 -27

No files matched your search

+8 -1
View File
@@ -63,6 +63,11 @@ export interface DiscordConfig {
scope: WebhookScope;
/** Tell each session how to reach the user (see DISCORD_STARTUP_HINT). On by default while the bridge is on. */
startupHint: boolean;
/**
* Also type the hint, once, as the first message to a brand-new non-Claude CLI tab. Off by default: it spends a
* model turn per tab, and for a spawned worker or a cron session it lands on the same first idle as the real prompt.
*/
typedHint: boolean;
}
export const DEFAULT_DISCORD_CONFIG: DiscordConfig = {
@@ -76,6 +81,7 @@ export const DEFAULT_DISCORD_CONFIG: DiscordConfig = {
pingMode: 'critical',
scope: 'attention',
startupHint: true,
typedHint: false,
};
export interface DiscordMessage {
@@ -271,6 +277,7 @@ function coerce(raw: unknown): DiscordConfig {
pingMode: oneOf(DISCORD_PING_MODES, r.pingMode, d.pingMode),
scope: oneOf(WEBHOOK_SCOPES, r.scope, d.scope),
startupHint: r.startupHint === undefined ? d.startupHint : r.startupHint === true,
typedHint: r.typedHint === true,
};
}
@@ -859,7 +866,7 @@ export class DiscordBridge {
*/
async sessionIdle(sessionId: string, hintable: boolean): Promise<void> {
if (!this.fresh.delete(sessionId)) return;
if (!hintable || !this.cfg.enabled || !this.cfg.startupHint) return;
if (!hintable || !this.cfg.enabled || !this.cfg.startupHint || !this.cfg.typedHint) return;
const text = sanitiseReply(`${DISCORD_STARTUP_HINT} Reply with just: ok`);
// fromUser false: this is Codeman talking, so it must not auto-name the tab or count as the person's input.
if (text) await this.deps.sendInput(sessionId, text, false).catch(() => false);
+9 -2
View File
@@ -3288,11 +3288,18 @@
</div>
<div class="set-row">
<div class="set-row-text">
<span class="set-row-label">Tell each CLI how to reach you</span>
<span class="set-row-desc">Claude is told at launch. Other CLIs are sent one short message when a new tab first becomes idle. It explains the codeman notify command, for when a CLI is blocked on something only you can do. Never sent to shell tabs.</span>
<span class="set-row-label">Tell Claude how to reach you</span>
<span class="set-row-desc">Adds a short note about the codeman notify command to Claude's system prompt at launch, for when it is blocked on something only you can do. Nothing appears in the conversation.</span>
</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, resumed, 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>
+2
View File
@@ -1770,6 +1770,7 @@ 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'}` };
@@ -1794,6 +1795,7 @@ 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)
+1
View File
@@ -2232,6 +2232,7 @@ 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();