@agentconnect.md/daemon 1.0.0-rc.236 → 1.0.0-rc.238
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-host-D_V0pKJA.js → acp-host-BADva9E0.js} +4 -4
- package/dist/acp-host-BADva9E0.js.map +1 -0
- package/dist/{chat-vW2um2DS.js → chat-C1jdOMwG.js} +2 -2
- package/dist/{chat-vW2um2DS.js.map → chat-C1jdOMwG.js.map} +1 -1
- package/dist/index.js +3 -3
- package/dist/{login-fEVCtx9V.js → login-Doh7jQUs.js} +3 -3
- package/dist/{login-fEVCtx9V.js.map → login-Doh7jQUs.js.map} +1 -1
- package/dist/run-foreground-BiAQhZyU.js +2 -0
- package/dist/{run-foreground-DKdGzA2H.js → run-foreground-R9O-KCZr.js} +381 -36
- package/dist/{run-foreground-DKdGzA2H.js.map → run-foreground-R9O-KCZr.js.map} +1 -1
- package/package.json +1 -1
- package/dist/acp-host-D_V0pKJA.js.map +0 -1
- package/dist/run-foreground-zCnpcNs6.js +0 -2
|
@@ -5,7 +5,7 @@ import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
|
|
|
5
5
|
import { C as _coercedNumber, T as NEVER, a as array, d as literal, f as looseObject, i as any, m as object, o as boolean, p as number$1, t as ZodNumber, v as string, x as url$4 } from "./schemas-CQkO9hC5.js";
|
|
6
6
|
import { a as agentChildEnv, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-C3pJSVeT.js";
|
|
7
7
|
import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
|
|
8
|
-
import { C as resolveRuntimes, S as cachedRuntimeVersions, _ as encode, a as initGitInjection, b as RESERVED_MCP_SERVER_NAME, c as simpleGit, d as RELAY_DAEMON_SUBPROTOCOL, f as RELAY_DAEMON_WS_PATH, g as decodeEnvelope, h as buildEnvelope, i as prepareWorkspace, m as decodeRelayDaemonFrame, n as installedRuntimes, o as probeGitVersion, p as buildRelayDaemonFrame, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as normalizeGitUrl, v as MAX_FRAME_BYTES, x as cachedRuntimeNames, y as MEMORY_INDEX } from "./acp-host-
|
|
8
|
+
import { C as resolveRuntimes, S as cachedRuntimeVersions, _ as encode, a as initGitInjection, b as RESERVED_MCP_SERVER_NAME, c as simpleGit, d as RELAY_DAEMON_SUBPROTOCOL, f as RELAY_DAEMON_WS_PATH, g as decodeEnvelope, h as buildEnvelope, i as prepareWorkspace, m as decodeRelayDaemonFrame, n as installedRuntimes, o as probeGitVersion, p as buildRelayDaemonFrame, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as normalizeGitUrl, v as MAX_FRAME_BYTES, x as cachedRuntimeNames, y as MEMORY_INDEX } from "./acp-host-BADva9E0.js";
|
|
9
9
|
import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-Dcsq6QJe.js";
|
|
10
10
|
import { B as ToolListChangedNotificationSchema, D as ListPromptsResultSchema, E as ListChangedOptionsBaseSchema, F as PromptListChangedNotificationSchema, G as isZ4Schema, H as isJSONRPCRequest, I as ReadResourceResultSchema, J as encodeFrame, K as safeParse, L as ResourceListChangedNotificationSchema, M as ListToolsResultSchema, O as ListResourceTemplatesResultSchema, P as McpError, R as SUPPORTED_PROTOCOL_VERSIONS, S as InitializeResultSchema, T as LATEST_PROTOCOL_VERSION, U as isJSONRPCResultResponse, V as isInitializedNotification, W as getObjectShape, _ as ElicitResultSchema, a as AjvJsonSchemaValidator, b as GetPromptResultSchema, c as mergeCapabilities, d as CompleteResultSchema, f as CreateMessageRequestSchema, g as ElicitRequestSchema, h as CreateTaskResultSchema, i as assertToolsCallTaskCapability, k as ListResourcesResultSchema, m as CreateMessageResultWithToolsSchema, n as serializeMessage, o as require_fast_deep_equal, p as CreateMessageResultSchema, q as decodeFrames, r as assertClientRequestTaskCapability, s as Protocol, t as ReadBuffer, u as CallToolResultSchema, v as EmptyResultSchema, w as JSONRPCMessageSchema, y as ErrorCode } from "./stdio-DnS8WcXi.js";
|
|
11
11
|
import { URL as URL$1 } from "url";
|
|
@@ -67655,7 +67655,7 @@ function clampTo$2(s, max) {
|
|
|
67655
67655
|
const t = s.trim();
|
|
67656
67656
|
return t.length > max ? `${t.slice(0, max - 1)}…` : t;
|
|
67657
67657
|
}
|
|
67658
|
-
function clampLabel(s) {
|
|
67658
|
+
function clampLabel$1(s) {
|
|
67659
67659
|
return clampTo$2(s, MAX_LABEL$2);
|
|
67660
67660
|
}
|
|
67661
67661
|
/** Wrap tool activity (a command line, tool title, or tool output) in a CommonMark code
|
|
@@ -67722,7 +67722,7 @@ function planIcon$2(status) {
|
|
|
67722
67722
|
/** Render an ACP plan (full entry list, resent on every update) as a compact summary.
|
|
67723
67723
|
* Posted as a `markdown` block, so emphasis is CommonMark (`**bold**`), not mrkdwn. */
|
|
67724
67724
|
function renderPlan$2(entries) {
|
|
67725
|
-
return [":clipboard: **Plan**", ...entries.map((e) => `${planIcon$2(e.status)} ${clampLabel(e.content ?? "")}`)].join("\n");
|
|
67725
|
+
return [":clipboard: **Plan**", ...entries.map((e) => `${planIcon$2(e.status)} ${clampLabel$1(e.content ?? "")}`)].join("\n");
|
|
67726
67726
|
}
|
|
67727
67727
|
/** Render the accumulated reasoning trace (high mode) as one in-place message. Posted as
|
|
67728
67728
|
* a `markdown` block (verbatim CommonMark, so `**bold**`), tail-clamped to a compact cap
|
|
@@ -67733,7 +67733,7 @@ function renderReasoning$2(buf) {
|
|
|
67733
67733
|
return `:thought_balloon: **Thinking**\n${trimmed.length > MAX_REASONING$2 ? `…${trimmed.slice(-2800)}` : trimmed}`;
|
|
67734
67734
|
}
|
|
67735
67735
|
/** Compact a token count as `1.2k` / `3.4M` (whole numbers under 1000 stay verbatim). */
|
|
67736
|
-
function compactCount$
|
|
67736
|
+
function compactCount$2(n) {
|
|
67737
67737
|
if (n < 1e3) return String(n);
|
|
67738
67738
|
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
67739
67739
|
return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -67749,9 +67749,9 @@ function renderStatusBar(info) {
|
|
|
67749
67749
|
if (info.fastMode) parts.push("fast");
|
|
67750
67750
|
if (info.contextUsed !== void 0 && info.contextSize !== void 0 && info.contextSize > 0) {
|
|
67751
67751
|
const pct = Math.round(info.contextUsed / info.contextSize * 100);
|
|
67752
|
-
parts.push(`ctx ${compactCount$
|
|
67753
|
-
} else if (info.contextUsed !== void 0) parts.push(`ctx ${compactCount$
|
|
67754
|
-
if (info.totalTokens !== void 0) parts.push(`${compactCount$
|
|
67752
|
+
parts.push(`ctx ${compactCount$2(info.contextUsed)}/${compactCount$2(info.contextSize)} (${pct}%)`);
|
|
67753
|
+
} else if (info.contextUsed !== void 0) parts.push(`ctx ${compactCount$2(info.contextUsed)}`);
|
|
67754
|
+
if (info.totalTokens !== void 0) parts.push(`${compactCount$2(info.totalTokens)} tok`);
|
|
67755
67755
|
return `:bar_chart: ${parts.length ? parts.join(" · ") : "—"}`;
|
|
67756
67756
|
}
|
|
67757
67757
|
/** Slack action_ids for the interactive status bar + modal (matched by the connection's
|
|
@@ -195004,12 +195004,14 @@ function humanizeDiscordText(text) {
|
|
|
195004
195004
|
/** Normalize a Discord gateway message into the platform-agnostic contract. */
|
|
195005
195005
|
function normalizeDiscordMessage(msg, ctx) {
|
|
195006
195006
|
const attachments = msg.attachments.map(toAttachment).filter((a) => a !== null);
|
|
195007
|
+
const isDm = !msg.inGuild;
|
|
195007
195008
|
return {
|
|
195008
195009
|
msgId: `discord:${msg.channelId}:${msg.id}`,
|
|
195009
195010
|
traceId: ctx.traceId,
|
|
195010
195011
|
source: "user",
|
|
195011
195012
|
platform: "discord",
|
|
195012
195013
|
channel: msg.channelId,
|
|
195014
|
+
thread: msg.channelId,
|
|
195013
195015
|
sender: {
|
|
195014
195016
|
id: msg.authorId,
|
|
195015
195017
|
isBot: msg.authorIsBot
|
|
@@ -195017,10 +195019,101 @@ function normalizeDiscordMessage(msg, ctx) {
|
|
|
195017
195019
|
text: humanizeDiscordText(msg.content),
|
|
195018
195020
|
mentionedBots: msg.mentionUserIds,
|
|
195019
195021
|
...attachments.length ? { attachments } : {},
|
|
195020
|
-
isDm
|
|
195022
|
+
isDm,
|
|
195023
|
+
...!isDm && !msg.isThread ? { discordTopLevel: true } : {}
|
|
195021
195024
|
};
|
|
195022
195025
|
}
|
|
195023
195026
|
//#endregion
|
|
195027
|
+
//#region src/discord/app-commands.ts
|
|
195028
|
+
/**
|
|
195029
|
+
* Native Discord application (slash) command definitions, registered globally on
|
|
195030
|
+
* connect (see DiscordConnection.start → registerCommands). They make the same
|
|
195031
|
+
* control vocabulary the daemon already parses from plain `/text` messages
|
|
195032
|
+
* (see commands.ts) show up in Discord's `/` autocomplete menu with descriptions
|
|
195033
|
+
* and typed arguments.
|
|
195034
|
+
*
|
|
195035
|
+
* These are NOT a second command engine: a slash invocation is reconstructed back
|
|
195036
|
+
* into its `/text` form (DiscordConnection.slashCommandText) and fed through the
|
|
195037
|
+
* SAME onInbound → parseCommand → handleCommand path as a typed message, so the
|
|
195038
|
+
* names/args here MUST stay in lockstep with parseCommand's vocabulary (guarded by
|
|
195039
|
+
* discord-app-commands.test.ts).
|
|
195040
|
+
*
|
|
195041
|
+
* PREREQUISITE: the bot must be invited with the `applications.commands` OAuth2
|
|
195042
|
+
* scope, else `commands.set` fails with 403 Missing Access and nothing appears.
|
|
195043
|
+
*
|
|
195044
|
+
* The select commands (`/models` `/effort` `/permission`) take an OPTIONAL free-text
|
|
195045
|
+
* argument — omitting it lists the choices as a tappable button card (buildDiscord-
|
|
195046
|
+
* SelectComponents); passing it selects directly. Choices aren't pre-declared because
|
|
195047
|
+
* the offered options are model-dependent and only known once a session is warm.
|
|
195048
|
+
*/
|
|
195049
|
+
const DISCORD_APP_COMMANDS = [
|
|
195050
|
+
{
|
|
195051
|
+
name: "status",
|
|
195052
|
+
description: "Show this session's model, context and token usage"
|
|
195053
|
+
},
|
|
195054
|
+
{
|
|
195055
|
+
name: "models",
|
|
195056
|
+
description: "List models, or switch (leave blank for a tappable picker)",
|
|
195057
|
+
options: [{
|
|
195058
|
+
name: "model",
|
|
195059
|
+
description: "Model id, name, or list number",
|
|
195060
|
+
type: import_src.ApplicationCommandOptionType.String
|
|
195061
|
+
}]
|
|
195062
|
+
},
|
|
195063
|
+
{
|
|
195064
|
+
name: "effort",
|
|
195065
|
+
description: "List reasoning-effort levels, or switch",
|
|
195066
|
+
options: [{
|
|
195067
|
+
name: "level",
|
|
195068
|
+
description: "Effort level or list number",
|
|
195069
|
+
type: import_src.ApplicationCommandOptionType.String
|
|
195070
|
+
}]
|
|
195071
|
+
},
|
|
195072
|
+
{
|
|
195073
|
+
name: "permission",
|
|
195074
|
+
description: "List permission modes, or switch",
|
|
195075
|
+
options: [{
|
|
195076
|
+
name: "mode",
|
|
195077
|
+
description: "Permission mode or list number",
|
|
195078
|
+
type: import_src.ApplicationCommandOptionType.String
|
|
195079
|
+
}]
|
|
195080
|
+
},
|
|
195081
|
+
{
|
|
195082
|
+
name: "fast",
|
|
195083
|
+
description: "Toggle fast mode for this session",
|
|
195084
|
+
options: [{
|
|
195085
|
+
name: "state",
|
|
195086
|
+
description: "on or off",
|
|
195087
|
+
type: import_src.ApplicationCommandOptionType.String,
|
|
195088
|
+
choices: [{
|
|
195089
|
+
name: "on",
|
|
195090
|
+
value: "on"
|
|
195091
|
+
}, {
|
|
195092
|
+
name: "off",
|
|
195093
|
+
value: "off"
|
|
195094
|
+
}]
|
|
195095
|
+
}]
|
|
195096
|
+
},
|
|
195097
|
+
{
|
|
195098
|
+
name: "stop",
|
|
195099
|
+
description: "Stop the agent and mute this thread until @mentioned"
|
|
195100
|
+
},
|
|
195101
|
+
{
|
|
195102
|
+
name: "cancel",
|
|
195103
|
+
description: "Cancel the in-flight turn (session stays live)"
|
|
195104
|
+
},
|
|
195105
|
+
{
|
|
195106
|
+
name: "queue",
|
|
195107
|
+
description: "Queue a message to run once the agent is idle",
|
|
195108
|
+
options: [{
|
|
195109
|
+
name: "message",
|
|
195110
|
+
description: "The message to queue",
|
|
195111
|
+
type: import_src.ApplicationCommandOptionType.String,
|
|
195112
|
+
required: true
|
|
195113
|
+
}]
|
|
195114
|
+
}
|
|
195115
|
+
];
|
|
195116
|
+
//#endregion
|
|
195024
195117
|
//#region src/discord/render.ts
|
|
195025
195118
|
/** Discord single-message hard cap (2000 chars) — vs Telegram's 4096, Slack's 12000-char block. */
|
|
195026
195119
|
const DISCORD_MESSAGE_LIMIT = 2e3;
|
|
@@ -195100,6 +195193,23 @@ function renderReasoning$1(buf) {
|
|
|
195100
195193
|
const trimmed = buf.trim();
|
|
195101
195194
|
return `💭 **Thinking**\n${trimmed.length > MAX_REASONING$1 ? `…${trimmed.slice(-2800)}` : trimmed}`;
|
|
195102
195195
|
}
|
|
195196
|
+
function compactCount$1(n) {
|
|
195197
|
+
if (n < 1e3) return String(n);
|
|
195198
|
+
if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
195199
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
195200
|
+
}
|
|
195201
|
+
/** The compact status line (markdown), e.g. `📊 **opus-4.8** · fast · ctx 120k/200k (60%) · 45.2k tok`. */
|
|
195202
|
+
function renderStatusText$1(info) {
|
|
195203
|
+
const parts = [];
|
|
195204
|
+
if (info.model) parts.push(`**${info.model}**`);
|
|
195205
|
+
if (info.fastMode) parts.push("fast");
|
|
195206
|
+
if (info.contextUsed !== void 0 && info.contextSize !== void 0 && info.contextSize > 0) {
|
|
195207
|
+
const pct = Math.round(info.contextUsed / info.contextSize * 100);
|
|
195208
|
+
parts.push(`ctx ${compactCount$1(info.contextUsed)}/${compactCount$1(info.contextSize)} (${pct}%)`);
|
|
195209
|
+
} else if (info.contextUsed !== void 0) parts.push(`ctx ${compactCount$1(info.contextUsed)}`);
|
|
195210
|
+
if (info.totalTokens !== void 0) parts.push(`${compactCount$1(info.totalTokens)} tok`);
|
|
195211
|
+
return `📊 ${parts.length ? parts.join(" · ") : "—"}`;
|
|
195212
|
+
}
|
|
195103
195213
|
/** custom_id verbs for the status button row. Discord caps custom_id at 100 chars
|
|
195104
195214
|
* but we keep the verb STATELESS (mirroring Telegram's callback_data) — the
|
|
195105
195215
|
* connection maps the button's message (channel+message_id) back to its session
|
|
@@ -195123,6 +195233,78 @@ function parseDiscordCallback(data) {
|
|
|
195123
195233
|
};
|
|
195124
195234
|
return null;
|
|
195125
195235
|
}
|
|
195236
|
+
/** One-char code ↔ kind for the compact select custom_id (`ac_sel:<code>:<index>`). */
|
|
195237
|
+
const DISCORD_SELECT_CODE = {
|
|
195238
|
+
model: "m",
|
|
195239
|
+
effort: "e",
|
|
195240
|
+
permission: "p"
|
|
195241
|
+
};
|
|
195242
|
+
const SELECT_CODE_KIND$1 = {
|
|
195243
|
+
m: "model",
|
|
195244
|
+
e: "effort",
|
|
195245
|
+
p: "permission"
|
|
195246
|
+
};
|
|
195247
|
+
const SELECT_PREFIX = "ac_sel";
|
|
195248
|
+
/** Discord caps a button label at 80 chars and an action row at 5 buttons / a message
|
|
195249
|
+
* at 5 rows → 25 options fit as a card; past that the daemon falls back to a text list. */
|
|
195250
|
+
const MAX_BUTTON_LABEL = 80;
|
|
195251
|
+
const MAX_BUTTONS_PER_ROW = 5;
|
|
195252
|
+
const MAX_SELECT_OPTIONS = MAX_BUTTONS_PER_ROW * 5;
|
|
195253
|
+
function clampLabel(s) {
|
|
195254
|
+
return s.length > MAX_BUTTON_LABEL ? `${s.slice(0, MAX_BUTTON_LABEL - 1)}…` : s;
|
|
195255
|
+
}
|
|
195256
|
+
/** Parse a select-button custom_id (`ac_sel:<code>:<index>`) into a kind + option index
|
|
195257
|
+
* (null if it isn't one). Stateless like the status verbs — the connection maps the
|
|
195258
|
+
* button's message back to its session key (see parseDiscordCallback's note). */
|
|
195259
|
+
function parseDiscordSelect(data) {
|
|
195260
|
+
const m = new RegExp(`^${SELECT_PREFIX}:([mep]):(\\d+)$`).exec(data);
|
|
195261
|
+
if (!m) return null;
|
|
195262
|
+
const kind = SELECT_CODE_KIND$1[m[1]];
|
|
195263
|
+
const index = Number(m[2]);
|
|
195264
|
+
if (!kind || !Number.isInteger(index)) return null;
|
|
195265
|
+
return {
|
|
195266
|
+
kind,
|
|
195267
|
+
index
|
|
195268
|
+
};
|
|
195269
|
+
}
|
|
195270
|
+
/**
|
|
195271
|
+
* Build a `/models` `/effort` `/permission` select as tappable button rows — one button
|
|
195272
|
+
* per option (the current flagged ✅, styled Success), custom_id = `ac_sel:<code>:<index>`.
|
|
195273
|
+
* The Discord analog of the daemon's buildSelectCard (Telegram inline keyboard). Returns
|
|
195274
|
+
* null when there are no options or too many to fit Discord's 25-button ceiling, so the
|
|
195275
|
+
* caller falls back to a numbered text list.
|
|
195276
|
+
*/
|
|
195277
|
+
function buildDiscordSelectComponents(kind, current, options) {
|
|
195278
|
+
if (options.length === 0 || options.length > MAX_SELECT_OPTIONS) return null;
|
|
195279
|
+
const code = DISCORD_SELECT_CODE[kind];
|
|
195280
|
+
const rows = [];
|
|
195281
|
+
for (let i = 0; i < options.length; i += MAX_BUTTONS_PER_ROW) rows.push({
|
|
195282
|
+
type: 1,
|
|
195283
|
+
components: options.slice(i, i + MAX_BUTTONS_PER_ROW).map((o, j) => {
|
|
195284
|
+
const isCurrent = o === current;
|
|
195285
|
+
return {
|
|
195286
|
+
type: 2,
|
|
195287
|
+
style: isCurrent ? 3 : 2,
|
|
195288
|
+
label: clampLabel(`${isCurrent ? "✅ " : ""}${o}`),
|
|
195289
|
+
custom_id: `${SELECT_PREFIX}:${code}:${i + j}`
|
|
195290
|
+
};
|
|
195291
|
+
})
|
|
195292
|
+
});
|
|
195293
|
+
return rows;
|
|
195294
|
+
}
|
|
195295
|
+
/** A lone "View session" link-button row (Discord style-5). Used by `/status`, where a
|
|
195296
|
+
* raw Slack `<url|text>` link would render as literal text on Discord. */
|
|
195297
|
+
function buildLinkComponents(link, label = "🔗 View session") {
|
|
195298
|
+
return [{
|
|
195299
|
+
type: 1,
|
|
195300
|
+
components: [{
|
|
195301
|
+
type: 2,
|
|
195302
|
+
style: 5,
|
|
195303
|
+
label,
|
|
195304
|
+
url: link
|
|
195305
|
+
}]
|
|
195306
|
+
}];
|
|
195307
|
+
}
|
|
195126
195308
|
var DiscordConverger = class {
|
|
195127
195309
|
mode;
|
|
195128
195310
|
buf = "";
|
|
@@ -195307,21 +195489,37 @@ var DiscordConnection = class {
|
|
|
195307
195489
|
}
|
|
195308
195490
|
});
|
|
195309
195491
|
client.on(import_src.Events.InteractionCreate, (interaction) => {
|
|
195492
|
+
if (interaction.isChatInputCommand()) {
|
|
195493
|
+
this.onSlashCommand(interaction);
|
|
195494
|
+
return;
|
|
195495
|
+
}
|
|
195310
195496
|
if (!interaction.isButton()) return;
|
|
195311
195497
|
interaction.deferUpdate().catch(() => {});
|
|
195312
|
-
const
|
|
195313
|
-
|
|
195314
|
-
const sessionKey = this.statusKeys.get(`${interaction.channelId}:${interaction.message.id}`);
|
|
195498
|
+
const channel = interaction.channelId;
|
|
195499
|
+
const sessionKey = this.statusKeys.get(`${channel}:${interaction.message.id}`);
|
|
195315
195500
|
if (!sessionKey) return;
|
|
195316
|
-
|
|
195317
|
-
|
|
195318
|
-
|
|
195319
|
-
|
|
195320
|
-
|
|
195321
|
-
|
|
195322
|
-
|
|
195323
|
-
|
|
195324
|
-
|
|
195501
|
+
const status = parseDiscordCallback(interaction.customId);
|
|
195502
|
+
if (status) {
|
|
195503
|
+
if (status.kind === "cancel") this.deps.onStatusAction?.({
|
|
195504
|
+
kind: "cancel",
|
|
195505
|
+
sessionKey
|
|
195506
|
+
});
|
|
195507
|
+
else this.deps.onStatusAction?.({
|
|
195508
|
+
kind: "set-fast",
|
|
195509
|
+
sessionKey,
|
|
195510
|
+
fastMode: status.fastMode
|
|
195511
|
+
});
|
|
195512
|
+
return;
|
|
195513
|
+
}
|
|
195514
|
+
const sel = parseDiscordSelect(interaction.customId);
|
|
195515
|
+
if (sel && this.deps.onSelectAction) {
|
|
195516
|
+
const rendered = this.deps.onSelectAction({
|
|
195517
|
+
kind: sel.kind,
|
|
195518
|
+
index: sel.index,
|
|
195519
|
+
sessionKey
|
|
195520
|
+
});
|
|
195521
|
+
if (rendered) this.updateMessage(channel, interaction.message.id, rendered.text, { keyboard: rendered.components });
|
|
195522
|
+
}
|
|
195325
195523
|
});
|
|
195326
195524
|
client.on(import_src.Events.Error, (err) => log?.debug(`discord: client error: ${err.message}`));
|
|
195327
195525
|
log?.debug("discord: login → opening Gateway…");
|
|
@@ -195330,11 +195528,68 @@ var DiscordConnection = class {
|
|
|
195330
195528
|
this.botUserId = c.user.id;
|
|
195331
195529
|
this.botUsername = c.user.username;
|
|
195332
195530
|
log?.debug(`discord: ready → bot @${this.botUsername} (id ${this.botUserId})`);
|
|
195531
|
+
this.registerCommands();
|
|
195333
195532
|
resolve();
|
|
195334
195533
|
});
|
|
195335
195534
|
client.login(this.botToken).catch(reject);
|
|
195336
195535
|
});
|
|
195337
195536
|
}
|
|
195537
|
+
/** Register the control vocabulary as global Discord application commands so it shows
|
|
195538
|
+
* up in the `/` autocomplete menu. Global registration covers every guild + DMs;
|
|
195539
|
+
* first-time propagation can take up to ~1h (edits are near-instant thereafter).
|
|
195540
|
+
* Best-effort — a 403 almost always means the bot wasn't invited with the
|
|
195541
|
+
* `applications.commands` scope, so we log that hint rather than throwing. */
|
|
195542
|
+
async registerCommands() {
|
|
195543
|
+
try {
|
|
195544
|
+
await this.client.application?.commands.set(DISCORD_APP_COMMANDS);
|
|
195545
|
+
this.deps.log?.info(`discord: registered ${DISCORD_APP_COMMANDS.length} slash commands (global) for @${this.botUsername}`);
|
|
195546
|
+
} catch (err) {
|
|
195547
|
+
this.deps.log?.error(`discord: slash-command registration failed — is the bot invited with the applications.commands scope? (${err.message})`);
|
|
195548
|
+
}
|
|
195549
|
+
}
|
|
195550
|
+
/**
|
|
195551
|
+
* A native slash-command invocation. Ack within Discord's 3s window (an ephemeral
|
|
195552
|
+
* echo, seen only by the invoker), then reconstruct the `/text` form and feed it
|
|
195553
|
+
* through the SAME onMessage path as a typed command — so routing, authz, the reply,
|
|
195554
|
+
* and the tappable cards all reuse handleCommand. The real output posts to the
|
|
195555
|
+
* channel/thread (public), matching how a typed `/command` behaves today.
|
|
195556
|
+
*/
|
|
195557
|
+
onSlashCommand(interaction) {
|
|
195558
|
+
const text = this.slashCommandText(interaction);
|
|
195559
|
+
interaction.reply({
|
|
195560
|
+
content: text,
|
|
195561
|
+
flags: import_src.MessageFlags.Ephemeral
|
|
195562
|
+
}).catch(() => {});
|
|
195563
|
+
try {
|
|
195564
|
+
const msg = normalizeDiscordMessage(this.slashToLike(interaction, text), { traceId: this.deps.newTraceId() });
|
|
195565
|
+
this.deps.log?.debug(`discord: slash ${text} ch=${msg.channel} user=${msg.sender.id}`);
|
|
195566
|
+
this.deps.onMessage(msg);
|
|
195567
|
+
} catch (err) {
|
|
195568
|
+
this.deps.log?.debug(`discord: slash command failed: ${err.message}`);
|
|
195569
|
+
}
|
|
195570
|
+
}
|
|
195571
|
+
/** Reconstruct a slash invocation into its `/text` form (`/models opus`, `/queue do X`)
|
|
195572
|
+
* so parseCommand handles it exactly like a typed message. Options are appended in
|
|
195573
|
+
* declaration order; our control commands take at most one. */
|
|
195574
|
+
slashCommandText(interaction) {
|
|
195575
|
+
const args = interaction.options.data.map((o) => o.value === void 0 || o.value === null ? "" : String(o.value)).join(" ").trim();
|
|
195576
|
+
return args ? `/${interaction.commandName} ${args}` : `/${interaction.commandName}`;
|
|
195577
|
+
}
|
|
195578
|
+
/** Adapt a slash interaction to the pure normalizer's plain-object view (text = the
|
|
195579
|
+
* reconstructed command). Mirrors toLike; no attachments/mentions on a command. */
|
|
195580
|
+
slashToLike(interaction, text) {
|
|
195581
|
+
return {
|
|
195582
|
+
id: interaction.id,
|
|
195583
|
+
channelId: interaction.channelId,
|
|
195584
|
+
content: text,
|
|
195585
|
+
authorId: interaction.user.id,
|
|
195586
|
+
authorIsBot: interaction.user.bot,
|
|
195587
|
+
inGuild: interaction.inGuild(),
|
|
195588
|
+
isThread: interaction.channel?.isThread() ?? false,
|
|
195589
|
+
mentionUserIds: [],
|
|
195590
|
+
attachments: []
|
|
195591
|
+
};
|
|
195592
|
+
}
|
|
195338
195593
|
/** Adapt a live gateway Message to the pure normalizer's plain-object view. */
|
|
195339
195594
|
toLike(message) {
|
|
195340
195595
|
return {
|
|
@@ -195344,6 +195599,7 @@ var DiscordConnection = class {
|
|
|
195344
195599
|
authorId: message.author.id,
|
|
195345
195600
|
authorIsBot: message.author.bot,
|
|
195346
195601
|
inGuild: message.inGuild(),
|
|
195602
|
+
isThread: message.channel.isThread(),
|
|
195347
195603
|
mentionUserIds: [...message.mentions.users.keys()],
|
|
195348
195604
|
attachments: [...message.attachments.values()].map((a) => ({
|
|
195349
195605
|
id: a.id,
|
|
@@ -195361,6 +195617,25 @@ var DiscordConnection = class {
|
|
|
195361
195617
|
return null;
|
|
195362
195618
|
}
|
|
195363
195619
|
/**
|
|
195620
|
+
* Open a thread off a channel message so a turn's reply + chrome live in the thread
|
|
195621
|
+
* rather than the parent channel (Slack-parity — see daemon's Discord ingress). The
|
|
195622
|
+
* created thread's id EQUALS the source message id (a Discord guarantee), so the
|
|
195623
|
+
* caller re-keys the session onto it and follow-ups posted in the thread (which
|
|
195624
|
+
* arrive with `channelId` = thread id) continue the same session. Best-effort:
|
|
195625
|
+
* returns the thread id, or undefined when the message/channel can't be resolved or
|
|
195626
|
+
* the bot lacks the Create Public Threads permission (caller then replies in-channel).
|
|
195627
|
+
*/
|
|
195628
|
+
async createThread(channelId, messageId, name) {
|
|
195629
|
+
try {
|
|
195630
|
+
const ch = await this.sendableChannel(channelId);
|
|
195631
|
+
if (!ch?.messages) return void 0;
|
|
195632
|
+
return (await (await ch.messages.fetch(messageId)).startThread({ name: name.slice(0, 100) || "Agent thread" })).id;
|
|
195633
|
+
} catch (err) {
|
|
195634
|
+
this.deps.log?.debug(`discord: createThread failed (ch=${channelId} msg=${messageId}): ${err.message}`);
|
|
195635
|
+
return;
|
|
195636
|
+
}
|
|
195637
|
+
}
|
|
195638
|
+
/**
|
|
195364
195639
|
* Post a message. `channel` is already the concrete channel/thread id (Discord
|
|
195365
195640
|
* threads are their own channels — see normalize.ts), so `threadTs` is unused —
|
|
195366
195641
|
* the session channel IS the reply target. Long text is chunked to the 2000-char
|
|
@@ -197342,10 +197617,12 @@ function applySpecFields(raw, spec, opts) {
|
|
|
197342
197617
|
raw.name = spec.name;
|
|
197343
197618
|
if (spec.description !== void 0) raw.description = spec.description;
|
|
197344
197619
|
if (spec.runtime !== void 0) raw.runtime = spec.runtime;
|
|
197345
|
-
if (spec.reasoningEffort !== void 0)
|
|
197620
|
+
if (spec.reasoningEffort !== void 0) if (spec.reasoningEffort === null) delete raw.reasoningEffort;
|
|
197621
|
+
else raw.reasoningEffort = spec.reasoningEffort;
|
|
197346
197622
|
if (spec.executionMode !== void 0) raw.executionMode = spec.executionMode;
|
|
197347
197623
|
if (spec.fastMode !== void 0) raw.fastMode = spec.fastMode;
|
|
197348
|
-
if (spec.permissionMode !== void 0)
|
|
197624
|
+
if (spec.permissionMode !== void 0) if (spec.permissionMode === null) delete raw.permissionMode;
|
|
197625
|
+
else raw.permissionMode = spec.permissionMode;
|
|
197349
197626
|
if (spec.pause !== void 0) raw.pause = spec.pause;
|
|
197350
197627
|
if (spec.mcpServers !== void 0) raw.mcpServers = spec.mcpServers;
|
|
197351
197628
|
if (spec.outputMode !== void 0) {
|
|
@@ -197355,7 +197632,8 @@ function applySpecFields(raw, spec, opts) {
|
|
|
197355
197632
|
}
|
|
197356
197633
|
if (spec.model !== void 0 || spec.env !== void 0) {
|
|
197357
197634
|
const ro = typeof raw.runtimeOverrides === "object" && raw.runtimeOverrides !== null ? raw.runtimeOverrides : {};
|
|
197358
|
-
if (spec.model !== void 0)
|
|
197635
|
+
if (spec.model !== void 0) if (spec.model === null) delete ro.model;
|
|
197636
|
+
else ro.model = spec.model;
|
|
197359
197637
|
if (spec.env !== void 0) ro.env = Object.entries(spec.env).map(([name, value]) => ({
|
|
197360
197638
|
name,
|
|
197361
197639
|
value
|
|
@@ -213460,6 +213738,12 @@ function pendingSessionKey(p) {
|
|
|
213460
213738
|
channel: p.channel
|
|
213461
213739
|
};
|
|
213462
213740
|
}
|
|
213741
|
+
/** Name for a Discord thread opened off a top-level @mention: the first line of the
|
|
213742
|
+
* prompt, collapsed to one line and clamped to Discord's 100-char thread-name cap
|
|
213743
|
+
* (createThread also clamps). Empty prompts (e.g. attachment-only) get a default. */
|
|
213744
|
+
function discordThreadName(text) {
|
|
213745
|
+
return text.replace(/\s+/g, " ").trim().slice(0, 90) || "Agent thread";
|
|
213746
|
+
}
|
|
213463
213747
|
var Daemon = class Daemon {
|
|
213464
213748
|
opts;
|
|
213465
213749
|
store;
|
|
@@ -213963,6 +214247,7 @@ var Daemon = class Daemon {
|
|
|
213963
214247
|
newTraceId: () => randomUUID(),
|
|
213964
214248
|
onMessage: (msg) => this.onInbound(msg),
|
|
213965
214249
|
onStatusAction: (a) => this.handleStatusAction(a),
|
|
214250
|
+
onSelectAction: (a) => this.handleDiscordSelect(a),
|
|
213966
214251
|
log: this.log
|
|
213967
214252
|
});
|
|
213968
214253
|
try {
|
|
@@ -214183,9 +214468,33 @@ var Daemon = class Daemon {
|
|
|
214183
214468
|
this.log.info(`routing: agent "${result.agentId}" un-muted in ch=${msg.channel} (explicit @mention)`);
|
|
214184
214469
|
}
|
|
214185
214470
|
this.log.info(`routing: ch=${msg.channel} → agent "${result.agentId}" (integration ${result.integrationId})`);
|
|
214471
|
+
if (msg.platform === "discord" && msg.discordTopLevel) {
|
|
214472
|
+
this.dispatchDiscordTopLevel(result.agentId, msg, result.integrationId).catch((err) => this.log.error(`dispatch failed for agent "${result.agentId}": ${formatErr(err)}`));
|
|
214473
|
+
return;
|
|
214474
|
+
}
|
|
214186
214475
|
this.dispatch(result.agentId, msg, result.integrationId).catch((err) => this.log.error(`dispatch failed for agent "${result.agentId}": ${formatErr(err)}`));
|
|
214187
214476
|
}
|
|
214188
214477
|
/**
|
|
214478
|
+
* Discord top-level channel @mention (§Slack-parity threading): open a thread off the
|
|
214479
|
+
* triggering message so the whole turn — reply + progress/status chrome — lands in a
|
|
214480
|
+
* thread instead of flooding the channel. The new thread's id equals the message id, so
|
|
214481
|
+
* we re-key the turn onto the thread channel; every follow-up posted there arrives with
|
|
214482
|
+
* `channelId` = thread id and normalizes to the SAME session, giving the conversation
|
|
214483
|
+
* continuity. Best-effort: if thread creation fails (e.g. the bot lacks Create Public
|
|
214484
|
+
* Threads), we fall back to replying in the channel (the pre-thread behavior).
|
|
214485
|
+
*/
|
|
214486
|
+
async dispatchDiscordTopLevel(agentId, msg, integrationId) {
|
|
214487
|
+
const conn = this.dcConnByIntegration.get(integrationId);
|
|
214488
|
+
const messageId = msg.msgId.split(":").pop() ?? "";
|
|
214489
|
+
const threadId = conn ? await conn.createThread(msg.channel, messageId, discordThreadName(msg.text)) : void 0;
|
|
214490
|
+
if (threadId) {
|
|
214491
|
+
this.log.info(`discord: opened thread ${threadId} for ch=${msg.channel} msg=${messageId}`);
|
|
214492
|
+
msg.channel = threadId;
|
|
214493
|
+
msg.thread = threadId;
|
|
214494
|
+
} else this.log.debug(`discord: no thread opened for ch=${msg.channel} — replying in channel`);
|
|
214495
|
+
await this.dispatch(agentId, msg, integrationId);
|
|
214496
|
+
}
|
|
214497
|
+
/**
|
|
214189
214498
|
* Handle an inbound webchat turn (C→D `webchat/message`). webchat is the one
|
|
214190
214499
|
* integration whose data plane is the daemon↔CP control WS itself — the far end is
|
|
214191
214500
|
* the CP/browser, so the body arrives inline and the reply streams back over the
|
|
@@ -214385,6 +214694,28 @@ var Daemon = class Daemon {
|
|
|
214385
214694
|
if (a.outputMode) this.setOutputModeByKey(a.sessionKey, a.outputMode);
|
|
214386
214695
|
}
|
|
214387
214696
|
}
|
|
214697
|
+
/**
|
|
214698
|
+
* A tapped Discord select-card button (`/models` `/effort` `/permission`): resolve the
|
|
214699
|
+
* session from the button's key, apply the chosen option, and return the re-rendered
|
|
214700
|
+
* card so the connection edits the message in place (new current flagged). Undefined
|
|
214701
|
+
* when the session is gone or the option index is stale (options changed) — the
|
|
214702
|
+
* connection then leaves the card as-is. Mirrors handleTelegramCallback.
|
|
214703
|
+
*/
|
|
214704
|
+
handleDiscordSelect(a) {
|
|
214705
|
+
const rec = this.store.getSession(a.sessionKey);
|
|
214706
|
+
if (!rec) return void 0;
|
|
214707
|
+
const info = this.statusInfoFrom(rec.agentId, a.sessionKey, rec.acpSessionId ?? void 0);
|
|
214708
|
+
const { options } = this.selectOptions(a.kind, info);
|
|
214709
|
+
const value = options[a.index];
|
|
214710
|
+
if (value === void 0) return void 0;
|
|
214711
|
+
this.applySelect(a.kind, a.sessionKey, value);
|
|
214712
|
+
const components = buildDiscordSelectComponents(a.kind, value, options);
|
|
214713
|
+
if (!components) return void 0;
|
|
214714
|
+
return {
|
|
214715
|
+
text: this.selectCardText(a.kind, value),
|
|
214716
|
+
components
|
|
214717
|
+
};
|
|
214718
|
+
}
|
|
214388
214719
|
/** All of our own connections' bot identities (both `U…` user ids and `B…`
|
|
214389
214720
|
* bot_ids), so we recognize self-echoes regardless of which field Slack sends. */
|
|
214390
214721
|
ownBotIdentities() {
|
|
@@ -214529,6 +214860,7 @@ var Daemon = class Daemon {
|
|
|
214529
214860
|
threadTs: replyThread,
|
|
214530
214861
|
replyTo: tgReplyTo
|
|
214531
214862
|
});
|
|
214863
|
+
else if (msg.platform === "discord") conn?.postChrome(msg.channel, renderStatusText$1(info), link ? { keyboard: buildLinkComponents(link) } : {});
|
|
214532
214864
|
else reply(link ? `${renderStatusBar(info)} · <${link}|View session>` : renderStatusBar(info));
|
|
214533
214865
|
return;
|
|
214534
214866
|
}
|
|
@@ -214550,11 +214882,23 @@ var Daemon = class Daemon {
|
|
|
214550
214882
|
reply("No active session here to configure.");
|
|
214551
214883
|
return;
|
|
214552
214884
|
}
|
|
214553
|
-
const
|
|
214554
|
-
|
|
214555
|
-
|
|
214556
|
-
|
|
214557
|
-
|
|
214885
|
+
const renderCard = msg.platform === "telegram" && conn ? (kind, current, options) => {
|
|
214886
|
+
const { text, buttons } = this.buildSelectCard(kind, current, options);
|
|
214887
|
+
conn.postCard(msg.channel, text, buttons, {
|
|
214888
|
+
threadTs: replyThread,
|
|
214889
|
+
replyTo: tgReplyTo
|
|
214890
|
+
});
|
|
214891
|
+
return true;
|
|
214892
|
+
} : msg.platform === "discord" && conn ? (kind, current, options) => {
|
|
214893
|
+
const components = buildDiscordSelectComponents(kind, current, options);
|
|
214894
|
+
if (!components) return false;
|
|
214895
|
+
conn.postChrome(msg.channel, this.selectCardText(kind, current), {
|
|
214896
|
+
keyboard: components,
|
|
214897
|
+
sessionKey: key
|
|
214898
|
+
});
|
|
214899
|
+
return true;
|
|
214900
|
+
} : void 0;
|
|
214901
|
+
this.handleSelectCommand(command.kind, command.value, target.agentId, key, acpSessionId ?? void 0, reply, renderCard);
|
|
214558
214902
|
return;
|
|
214559
214903
|
}
|
|
214560
214904
|
if (!command.text) {
|
|
@@ -214606,6 +214950,11 @@ var Daemon = class Daemon {
|
|
|
214606
214950
|
else if (kind === "effort") this.setEffortByKey(key, value);
|
|
214607
214951
|
else this.setPermissionModeByKey(key, value);
|
|
214608
214952
|
}
|
|
214953
|
+
/** Header line for a select card (shared by the Telegram inline-keyboard card and the
|
|
214954
|
+
* Discord button card). */
|
|
214955
|
+
selectCardText(kind, current) {
|
|
214956
|
+
return `${this.selectLabel(kind)} — tap to switch (current: ${current ?? "default"}):`;
|
|
214957
|
+
}
|
|
214609
214958
|
/** Build a session-control card: a header line + one tappable button per option (the
|
|
214610
214959
|
* current one flagged), with `callback_data` = `<kindCode>:<optionIndex>`. */
|
|
214611
214960
|
buildSelectCard(kind, current, options) {
|
|
@@ -214615,7 +214964,7 @@ var Daemon = class Daemon {
|
|
|
214615
214964
|
callbackData: `${code}:${i}`
|
|
214616
214965
|
}]);
|
|
214617
214966
|
return {
|
|
214618
|
-
text:
|
|
214967
|
+
text: this.selectCardText(kind, current),
|
|
214619
214968
|
buttons
|
|
214620
214969
|
};
|
|
214621
214970
|
}
|
|
@@ -214627,7 +214976,7 @@ var Daemon = class Daemon {
|
|
|
214627
214976
|
* live host's config selectors (statusInfoFrom); when the host is cold a given value is
|
|
214628
214977
|
* accepted optimistically and takes effect on the next turn.
|
|
214629
214978
|
*/
|
|
214630
|
-
handleSelectCommand(kind, value, agentId, key, acpSessionId, reply,
|
|
214979
|
+
handleSelectCommand(kind, value, agentId, key, acpSessionId, reply, renderCard) {
|
|
214631
214980
|
const label = this.selectLabel(kind);
|
|
214632
214981
|
const info = this.statusInfoFrom(agentId, key, acpSessionId);
|
|
214633
214982
|
const { current, options } = this.selectOptions(kind, info);
|
|
@@ -214637,11 +214986,7 @@ var Daemon = class Daemon {
|
|
|
214637
214986
|
reply(current ? `${label}: ${current} (no other options offered${kind === "effort" ? " — the current model may not support effort" : ""}).` : `No ${label.toLowerCase()} options available yet — send me a message first, then try /${cmd} again.`);
|
|
214638
214987
|
return;
|
|
214639
214988
|
}
|
|
214640
|
-
if (
|
|
214641
|
-
const { text, buttons } = this.buildSelectCard(kind, current, options);
|
|
214642
|
-
card(text, buttons);
|
|
214643
|
-
return;
|
|
214644
|
-
}
|
|
214989
|
+
if (renderCard?.(kind, current, options)) return;
|
|
214645
214990
|
reply(`${label} — reply \`/${cmd} <name or number>\`:\n${options.map((o, i) => `${i + 1}. ${o}${o === current ? " ✓ (current)" : ""}`).join("\n")}`);
|
|
214646
214991
|
return;
|
|
214647
214992
|
}
|
|
@@ -216062,4 +216407,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
216062
216407
|
//#endregion
|
|
216063
216408
|
export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
|
|
216064
216409
|
|
|
216065
|
-
//# sourceMappingURL=run-foreground-
|
|
216410
|
+
//# sourceMappingURL=run-foreground-R9O-KCZr.js.map
|