botmux 2.83.0 → 2.84.1
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.en.md +1 -1
- package/README.md +5 -1
- package/dist/adapters/backend/sandbox.d.ts +4 -0
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +14 -0
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +6 -43
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/mir.d.ts +4 -0
- package/dist/adapters/cli/mir.d.ts.map +1 -0
- package/dist/adapters/cli/mir.js +81 -0
- package/dist/adapters/cli/mir.js.map +1 -0
- package/dist/adapters/cli/registry.d.ts +2 -1
- package/dist/adapters/cli/registry.d.ts.map +1 -1
- package/dist/adapters/cli/registry.js +3 -1
- package/dist/adapters/cli/registry.js.map +1 -1
- package/dist/adapters/cli/shared-hints.d.ts +17 -0
- package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
- package/dist/adapters/cli/shared-hints.js +56 -0
- package/dist/adapters/cli/shared-hints.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +12 -1
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/bot-registry.d.ts +7 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +27 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +30 -65
- package/dist/cli.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +42 -1
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +58 -1
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/passthrough-commands.d.ts.map +1 -1
- package/dist/core/passthrough-commands.js +1 -1
- package/dist/core/passthrough-commands.js.map +1 -1
- package/dist/core/pending-response.d.ts +2 -39
- package/dist/core/pending-response.d.ts.map +1 -1
- package/dist/core/pending-response.js +5 -99
- package/dist/core/pending-response.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +4 -16
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/skills/claude-plugin-delivery.d.ts +6 -0
- package/dist/core/skills/claude-plugin-delivery.d.ts.map +1 -0
- package/dist/core/skills/claude-plugin-delivery.js +21 -0
- package/dist/core/skills/claude-plugin-delivery.js.map +1 -0
- package/dist/core/skills/cli-admin-command.d.ts +7 -0
- package/dist/core/skills/cli-admin-command.d.ts.map +1 -0
- package/dist/core/skills/cli-admin-command.js +243 -0
- package/dist/core/skills/cli-admin-command.js.map +1 -0
- package/dist/core/skills/cli-session-command.d.ts +7 -0
- package/dist/core/skills/cli-session-command.d.ts.map +1 -0
- package/dist/core/skills/cli-session-command.js +45 -0
- package/dist/core/skills/cli-session-command.js.map +1 -0
- package/dist/core/skills/delivery.d.ts +11 -0
- package/dist/core/skills/delivery.d.ts.map +1 -0
- package/dist/core/skills/delivery.js +22 -0
- package/dist/core/skills/delivery.js.map +1 -0
- package/dist/core/skills/discovery.d.ts +3 -0
- package/dist/core/skills/discovery.d.ts.map +1 -0
- package/dist/core/skills/discovery.js +34 -0
- package/dist/core/skills/discovery.js.map +1 -0
- package/dist/core/skills/frontmatter.d.ts +9 -0
- package/dist/core/skills/frontmatter.d.ts.map +1 -0
- package/dist/core/skills/frontmatter.js +42 -0
- package/dist/core/skills/frontmatter.js.map +1 -0
- package/dist/core/skills/im-command.d.ts +9 -0
- package/dist/core/skills/im-command.d.ts.map +1 -0
- package/dist/core/skills/im-command.js +107 -0
- package/dist/core/skills/im-command.js.map +1 -0
- package/dist/core/skills/manifest-store.d.ts +4 -0
- package/dist/core/skills/manifest-store.d.ts.map +1 -0
- package/dist/core/skills/manifest-store.js +26 -0
- package/dist/core/skills/manifest-store.js.map +1 -0
- package/dist/core/skills/package.d.ts +13 -0
- package/dist/core/skills/package.d.ts.map +1 -0
- package/dist/core/skills/package.js +35 -0
- package/dist/core/skills/package.js.map +1 -0
- package/dist/core/skills/policy.d.ts +18 -0
- package/dist/core/skills/policy.d.ts.map +1 -0
- package/dist/core/skills/policy.js +69 -0
- package/dist/core/skills/policy.js.map +1 -0
- package/dist/core/skills/prompt.d.ts +3 -0
- package/dist/core/skills/prompt.d.ts.map +1 -0
- package/dist/core/skills/prompt.js +25 -0
- package/dist/core/skills/prompt.js.map +1 -0
- package/dist/core/skills/references.d.ts +21 -0
- package/dist/core/skills/references.d.ts.map +1 -0
- package/dist/core/skills/references.js +27 -0
- package/dist/core/skills/references.js.map +1 -0
- package/dist/core/skills/registry-paths.d.ts +5 -0
- package/dist/core/skills/registry-paths.d.ts.map +1 -0
- package/dist/core/skills/registry-paths.js +15 -0
- package/dist/core/skills/registry-paths.js.map +1 -0
- package/dist/core/skills/resource-reader.d.ts +9 -0
- package/dist/core/skills/resource-reader.d.ts.map +1 -0
- package/dist/core/skills/resource-reader.js +97 -0
- package/dist/core/skills/resource-reader.js.map +1 -0
- package/dist/core/skills/session-resolver.d.ts +14 -0
- package/dist/core/skills/session-resolver.d.ts.map +1 -0
- package/dist/core/skills/session-resolver.js +24 -0
- package/dist/core/skills/session-resolver.js.map +1 -0
- package/dist/core/skills/session-runtime.d.ts +14 -0
- package/dist/core/skills/session-runtime.d.ts.map +1 -0
- package/dist/core/skills/session-runtime.js +32 -0
- package/dist/core/skills/session-runtime.js.map +1 -0
- package/dist/core/skills/sources.d.ts +21 -0
- package/dist/core/skills/sources.d.ts.map +1 -0
- package/dist/core/skills/sources.js +155 -0
- package/dist/core/skills/sources.js.map +1 -0
- package/dist/core/skills/types.d.ts +71 -0
- package/dist/core/skills/types.d.ts.map +1 -0
- package/dist/core/skills/types.js +2 -0
- package/dist/core/skills/types.js.map +1 -0
- package/dist/core/types.d.ts +10 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +14 -1
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +105 -69
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +2 -2
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +49 -52
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/skill-install-request.d.ts +21 -0
- package/dist/dashboard/skill-install-request.d.ts.map +1 -0
- package/dist/dashboard/skill-install-request.js +62 -0
- package/dist/dashboard/skill-install-request.js.map +1 -0
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +4 -1
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +138 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/skills.d.ts +2 -0
- package/dist/dashboard/web/skills.d.ts.map +1 -0
- package/dist/dashboard/web/skills.js +539 -0
- package/dist/dashboard/web/skills.js.map +1 -0
- package/dist/dashboard/web/workflows.js +1 -1
- package/dist/dashboard/web/workflows.js.map +1 -1
- package/dist/dashboard-web/app.js +594 -451
- package/dist/dashboard-web/index.html +1 -0
- package/dist/dashboard-web/style.css +793 -0
- package/dist/dashboard.js +231 -0
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +7 -0
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +16 -0
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +2 -5
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +2 -5
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +0 -3
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +1 -33
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/mir-local-runtime.d.ts +20 -0
- package/dist/mir-local-runtime.d.ts.map +1 -0
- package/dist/mir-local-runtime.js +168 -0
- package/dist/mir-local-runtime.js.map +1 -0
- package/dist/mir-runner.d.ts +3 -0
- package/dist/mir-runner.d.ts.map +1 -0
- package/dist/mir-runner.js +482 -0
- package/dist/mir-runner.js.map +1 -0
- package/dist/services/bot-config-store.d.ts +4 -4
- package/dist/services/bot-config-store.d.ts.map +1 -1
- package/dist/services/bot-config-store.js +24 -1
- package/dist/services/bot-config-store.js.map +1 -1
- package/dist/services/session-store.d.ts +1 -0
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +12 -5
- package/dist/services/session-store.js.map +1 -1
- package/dist/services/skill-registry-store.d.ts +42 -0
- package/dist/services/skill-registry-store.d.ts.map +1 -0
- package/dist/services/skill-registry-store.js +343 -0
- package/dist/services/skill-registry-store.js.map +1 -0
- package/dist/setup/bot-config-editor.d.ts.map +1 -1
- package/dist/setup/bot-config-editor.js +2 -0
- package/dist/setup/bot-config-editor.js.map +1 -1
- package/dist/setup/cli-selection.d.ts.map +1 -1
- package/dist/setup/cli-selection.js +74 -6
- package/dist/setup/cli-selection.js.map +1 -1
- package/dist/skills/installer.d.ts.map +1 -1
- package/dist/skills/installer.js +3 -0
- package/dist/skills/installer.js.map +1 -1
- package/dist/types.d.ts +2 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/file-lock.d.ts +1 -0
- package/dist/utils/file-lock.d.ts.map +1 -1
- package/dist/utils/file-lock.js +87 -1
- package/dist/utils/file-lock.js.map +1 -1
- package/dist/worker.js +5 -3
- package/dist/worker.js.map +1 -1
- package/dist/workflows/attempt-resume.d.ts.map +1 -1
- package/dist/workflows/attempt-resume.js +1 -1
- package/dist/workflows/attempt-resume.js.map +1 -1
- package/dist/workflows/definition.d.ts +16 -16
- package/dist/workflows/events/schema.d.ts +280 -280
- package/package.json +1 -1
- package/dist/services/pending-response-transaction-store.d.ts +0 -12
- package/dist/services/pending-response-transaction-store.d.ts.map +0 -1
- package/dist/services/pending-response-transaction-store.js +0 -52
- package/dist/services/pending-response-transaction-store.js.map +0 -1
package/dist/core/worker-pool.js
CHANGED
|
@@ -16,10 +16,9 @@ import { config } from '../config.js';
|
|
|
16
16
|
import * as sessionStore from '../services/session-store.js';
|
|
17
17
|
import { persistStreamCardState, rememberLastCliInput } from './session-manager.js';
|
|
18
18
|
import { fallbackTurnId } from './reply-target.js';
|
|
19
|
-
import { updateMessage, deleteMessage, sendEphemeralCard, sendUserMessage, addReaction, MessageWithdrawnError } from '../im/lark/client.js';
|
|
19
|
+
import { updateMessage, deleteMessage, sendEphemeralCard, sendUserMessage, addReaction, removeReaction, MessageWithdrawnError } from '../im/lark/client.js';
|
|
20
20
|
import { buildStreamingCard, buildPrivateSnapshotCard, buildSessionCard, buildTuiPromptCard, buildTuiPromptResolvedCard, buildRelayedFrozenCard, getCliDisplayName } from '../im/lark/card-builder.js';
|
|
21
21
|
import { loadFrozenCards, saveFrozenCards } from '../services/frozen-card-store.js';
|
|
22
|
-
import { clearPendingResponsePatchMarker, markPendingResponsePatchMarkerPatched, writePendingResponsePatchMarker } from '../services/pending-response-transaction-store.js';
|
|
23
22
|
import { logger } from '../utils/logger.js';
|
|
24
23
|
import { createCliAdapterSync } from '../adapters/cli/registry.js';
|
|
25
24
|
import { botLocale, localeForBot, t as tr } from '../i18n/index.js';
|
|
@@ -39,8 +38,10 @@ import { publishAttentionPatch } from './session-activity.js';
|
|
|
39
38
|
import { knownBotOpenIdsFromCrossRef } from '../utils/bot-routing.js';
|
|
40
39
|
import { emitSessionLifecycleHook, emitSessionStateTransitionHook } from '../services/session-lifecycle-hooks.js';
|
|
41
40
|
import { anchorUsageForDaemonSession, recordOwnershipForDaemonSession, recordUsageForDaemonSession, reconcileUsageForDaemonSession } from '../services/usage-ledger.js';
|
|
41
|
+
import { prepareSessionSkillPrompt } from './skills/session-runtime.js';
|
|
42
|
+
import { prepareSkillDelivery } from './skills/delivery.js';
|
|
42
43
|
import { sessionKey, sessionAnchorId } from './types.js';
|
|
43
|
-
import {
|
|
44
|
+
import { DONE_REACTION_EMOJI_TYPE } from './pending-response.js';
|
|
44
45
|
import { buildTerminalUrl } from './terminal-url.js';
|
|
45
46
|
import { prependBotmuxBin } from './botmux-wrapper.js';
|
|
46
47
|
import { usageLimitStateKey } from '../utils/cli-usage-limit.js';
|
|
@@ -185,13 +186,21 @@ function loadKnownBotOpenIdsForApp(larkAppId) {
|
|
|
185
186
|
}
|
|
186
187
|
return knownBotOpenIdsFromCrossRef(crossRef, botEntries, larkAppId);
|
|
187
188
|
}
|
|
189
|
+
/** CLIs whose model→Lark delivery is the daemon's stdout-runner fallback card
|
|
190
|
+
* (NOT the model calling `botmux send`): mira (Web API runner) and mir (local
|
|
191
|
+
* mircli runner). They can't @-trigger a peer bot themselves, so for bot-to-bot
|
|
192
|
+
* handoffs the fallback card must carry the real <at> back to the dispatcher. */
|
|
193
|
+
function isRunnerDeliveryCli(cliId) {
|
|
194
|
+
return cliId === 'mira' || cliId === 'mir';
|
|
195
|
+
}
|
|
188
196
|
function daemonCardFooterRecipientOpenId(ds, effectiveCliId) {
|
|
189
197
|
const owner = ds.session.ownerOpenId;
|
|
190
198
|
if (!owner) {
|
|
191
|
-
// Mira
|
|
192
|
-
//
|
|
193
|
-
// to the original dispatcher so orchestration
|
|
194
|
-
|
|
199
|
+
// Mira / Mir run through botmux's stdout-runner and cannot execute
|
|
200
|
+
// `botmux send` to @-trigger a peer bot. For bot-to-bot handoffs, address
|
|
201
|
+
// the daemon fallback card back to the original dispatcher so orchestration
|
|
202
|
+
// resumes (the card's real <at> is what re-wakes the dispatching bot).
|
|
203
|
+
if (isRunnerDeliveryCli(effectiveCliId) && ds.session.quoteTargetSenderIsBot && ds.session.creatorOpenId) {
|
|
195
204
|
return ds.session.creatorOpenId;
|
|
196
205
|
}
|
|
197
206
|
return undefined;
|
|
@@ -200,9 +209,10 @@ function daemonCardFooterRecipientOpenId(ds, effectiveCliId) {
|
|
|
200
209
|
if (loadKnownBotOpenIdsForApp(ds.larkAppId).has(owner)) {
|
|
201
210
|
// `/repo`-primed dispatch records the dispatching bot as owner (unlike
|
|
202
211
|
// the @-mention auto-create path, which nulls ownerOpenId for bot
|
|
203
|
-
// senders). Same
|
|
204
|
-
// only
|
|
205
|
-
|
|
212
|
+
// senders). Same constraint for the stdout-runner CLIs (mira/mir): the
|
|
213
|
+
// daemon fallback card is their only @-trigger channel, so address the
|
|
214
|
+
// dispatcher bot here too.
|
|
215
|
+
return isRunnerDeliveryCli(effectiveCliId) ? owner : undefined;
|
|
206
216
|
}
|
|
207
217
|
return owner;
|
|
208
218
|
}
|
|
@@ -1348,6 +1358,16 @@ export function forkWorker(ds, prompt, resume = false) {
|
|
|
1348
1358
|
const cwd = rawCwd && existsSync(rawCwd) ? rawCwd : homedir();
|
|
1349
1359
|
if (cwd !== rawCwd)
|
|
1350
1360
|
logger.warn(`[${t}] workingDir "${rawCwd}" does not exist — falling back to ${cwd}`);
|
|
1361
|
+
// Materialise the resolved launch dir on the live session. getSessionWorkingDir()
|
|
1362
|
+
// falls back to the bot-default workingDir, but the usage ledger and dashboard read
|
|
1363
|
+
// `ds.workingDir ?? s.workingDir` RAW (without that fallback). A session that inherits
|
|
1364
|
+
// the bot-default workingDir — i.e. one never pinned via /repo or /cd — therefore leaves
|
|
1365
|
+
// ds.workingDir undefined, so getSessionTokenUsage() is handed cwd=undefined, cannot
|
|
1366
|
+
// locate the CLI transcript, and the session's token usage silently never records.
|
|
1367
|
+
// Pinning the resolved cwd here (it equals what the worker actually forked into) closes
|
|
1368
|
+
// that gap without touching the persisted session.workingDir "unset = follow default"
|
|
1369
|
+
// semantics: this is re-derived on every fork/restore.
|
|
1370
|
+
ds.workingDir = cwd;
|
|
1351
1371
|
// Sandbox decision is RECORDED ON THE SESSION at creation and reused on
|
|
1352
1372
|
// restore — so toggling the live bot flag never retroactively (un)sandboxes a
|
|
1353
1373
|
// historical session. A brand-new session (resume=false) with no recorded
|
|
@@ -1395,6 +1415,33 @@ export function forkWorker(ds, prompt, resume = false) {
|
|
|
1395
1415
|
const familyAdapter = createCliAdapterSync(botCfg.cliId, botCfg.cliPathOverride);
|
|
1396
1416
|
if (familyAdapter.claudeStateJsonPath)
|
|
1397
1417
|
ensureClaudeFolderTrust(cwd, familyAdapter.claudeStateJsonPath);
|
|
1418
|
+
let skillPluginDir;
|
|
1419
|
+
let skillReadonlyRoots;
|
|
1420
|
+
if (!resume && prompt.trim().length > 0) {
|
|
1421
|
+
const preparedSkills = prepareSessionSkillPrompt({
|
|
1422
|
+
sessionId: ds.session.sessionId,
|
|
1423
|
+
cliId: botCfg.cliId,
|
|
1424
|
+
workingDir: cwd,
|
|
1425
|
+
prompt,
|
|
1426
|
+
botPolicy: botCfg.skills,
|
|
1427
|
+
});
|
|
1428
|
+
prompt = preparedSkills.prompt;
|
|
1429
|
+
const delivery = prepareSkillDelivery(familyAdapter, preparedSkills.manifest, preparedSkills.manifest?.delivery ?? 'auto');
|
|
1430
|
+
skillPluginDir = delivery.pluginDir;
|
|
1431
|
+
skillReadonlyRoots = delivery.readonlyRoots.length ? delivery.readonlyRoots : undefined;
|
|
1432
|
+
for (const diagnostic of delivery.diagnostics)
|
|
1433
|
+
logger.warn(`[${t}] skill delivery: ${diagnostic}`);
|
|
1434
|
+
if (delivery.fatal) {
|
|
1435
|
+
const reason = delivery.diagnostics.join(', ') || 'unknown';
|
|
1436
|
+
const message = tr('worker.skill_delivery_failed', { reason }, botLocale(botCfg));
|
|
1437
|
+
logger.warn(`[${t}] Skill delivery blocked session start: ${reason}`);
|
|
1438
|
+
void cb.sessionReply(sessionAnchorId(ds), message, undefined, ds.larkAppId, fallbackTurnId(ds, undefined))
|
|
1439
|
+
.catch((err) => logger.warn(`[${t}] Failed to notify skill delivery error: ${err?.message ?? err}`));
|
|
1440
|
+
void closeSession(ds.session.sessionId)
|
|
1441
|
+
.catch((err) => logger.warn(`[${t}] Failed to close skill delivery error session: ${err?.message ?? err}`));
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1398
1445
|
// Prepend ~/.botmux/bin to PATH so CLIs can call `botmux send` etc.
|
|
1399
1446
|
// The wrapper script there is written by the daemon at startup.
|
|
1400
1447
|
const botmuxBinDir = join(homedir(), '.botmux', 'bin');
|
|
@@ -1409,6 +1456,7 @@ export function forkWorker(ds, prompt, resume = false) {
|
|
|
1409
1456
|
CLAUDECODE: undefined,
|
|
1410
1457
|
BOTMUX: '1', // Marker so user scripts/skills can detect a botmux-spawned CLI
|
|
1411
1458
|
SESSION_DATA_DIR: config.session.dataDir,
|
|
1459
|
+
BOTMUX_SESSION_ID: ds.session.sessionId,
|
|
1412
1460
|
LARK_APP_ID: botCfg.larkAppId,
|
|
1413
1461
|
LARK_APP_SECRET: botCfg.larkAppSecret,
|
|
1414
1462
|
},
|
|
@@ -1465,6 +1513,8 @@ export function forkWorker(ds, prompt, resume = false) {
|
|
|
1465
1513
|
botOpenId: bot.botOpenId,
|
|
1466
1514
|
locale: botLocale(botCfg),
|
|
1467
1515
|
turnId: ds.currentReplyTarget?.turnId,
|
|
1516
|
+
skillPluginDir,
|
|
1517
|
+
skillReadonlyRoots,
|
|
1468
1518
|
};
|
|
1469
1519
|
worker.send(initMsg);
|
|
1470
1520
|
ds.initConfig = initMsg;
|
|
@@ -1727,10 +1777,12 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
1727
1777
|
source: 'screen_update',
|
|
1728
1778
|
content: msg.content,
|
|
1729
1779
|
});
|
|
1730
|
-
// Usage ledger: idle/limited edges are turn
|
|
1731
|
-
// token delta
|
|
1780
|
+
// Usage ledger + turn reactions: idle/limited edges are turn
|
|
1781
|
+
// boundaries. Append the token delta, and flip this turn's pending ✋
|
|
1782
|
+
// reactions to ✅ (best-effort, never blocks the status pipeline).
|
|
1732
1783
|
if (ds.lastScreenStatus === 'idle' || ds.lastScreenStatus === 'limited') {
|
|
1733
1784
|
recordUsageForDaemonSession(ds);
|
|
1785
|
+
void finishTurnReactions(ds);
|
|
1734
1786
|
}
|
|
1735
1787
|
}
|
|
1736
1788
|
// Bot opted out of the streaming card — dashboard SSE above already got
|
|
@@ -2038,18 +2090,50 @@ function setupWorkerHandlers(ds, worker) {
|
|
|
2038
2090
|
}
|
|
2039
2091
|
// ─── Bridge final-output delivery (with retry) ──────────────────────────────
|
|
2040
2092
|
const FINAL_OUTPUT_RETRY_BACKOFF_MS = [0, 5000, 15000]; // immediate, +5s, +15s
|
|
2093
|
+
/**
|
|
2094
|
+
* Turn-end half of the two-phase turn reactions (auto-on for card-off sessions,
|
|
2095
|
+
* i.e. streaming card disabled). The 冲! "received" reactions are added per-message at the daemon
|
|
2096
|
+
* acceptance point (`noteTurnReceived`); when the worker next returns to idle we
|
|
2097
|
+
* flip every pending ✋ on this session to ✅ DONE and clear the list. Binding the
|
|
2098
|
+
* start to the message (not a status edge) means type-ahead / busy-batched
|
|
2099
|
+
* messages each get their own reaction and all settle together here.
|
|
2100
|
+
*
|
|
2101
|
+
* Every Feishu call is best-effort — a failure only means a missing emoji, so it
|
|
2102
|
+
* must never throw into the status pipeline (callers invoke as `void`).
|
|
2103
|
+
*/
|
|
2104
|
+
async function finishTurnReactions(ds) {
|
|
2105
|
+
const list = ds.pendingAckReactions;
|
|
2106
|
+
if (!list || list.length === 0)
|
|
2107
|
+
return;
|
|
2108
|
+
// Detach the batch first so a second idle edge can't double-flip it.
|
|
2109
|
+
ds.pendingAckReactions = [];
|
|
2110
|
+
for (const ack of list) {
|
|
2111
|
+
if (ack.reactionId) {
|
|
2112
|
+
try {
|
|
2113
|
+
await removeReaction(ds.larkAppId, ack.messageId, ack.reactionId);
|
|
2114
|
+
}
|
|
2115
|
+
catch (err) {
|
|
2116
|
+
logger.debug(`[reaction] failed to remove received reaction ${ack.reactionId}: ${err?.message ?? err}`);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
try {
|
|
2120
|
+
await addReaction(ds.larkAppId, ack.messageId, DONE_REACTION_EMOJI_TYPE);
|
|
2121
|
+
}
|
|
2122
|
+
catch (err) {
|
|
2123
|
+
logger.debug(`[reaction] failed to add done reaction to ${ack.messageId}: ${err?.message ?? err}`);
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2041
2127
|
/** Deliver a bridge `final_output` to Lark. The worker emits each turn
|
|
2042
2128
|
* exactly once (it pops the turn off its queue at emit time), so the
|
|
2043
2129
|
* daemon owns retries on transient failures. After 3 attempts we log
|
|
2044
2130
|
* and give up — the user's answer is lost; better than leaking memory
|
|
2045
2131
|
* via an unbounded retry loop. */
|
|
2046
|
-
function deliverFinalOutput(ds, msg, t, attempt
|
|
2132
|
+
function deliverFinalOutput(ds, msg, t, attempt) {
|
|
2047
2133
|
const cb = requireCallbacks();
|
|
2048
2134
|
const effectiveCliId = ds.session.cliId ?? getBot(ds.larkAppId).config.cliId;
|
|
2049
2135
|
const scopedReply = (content, msgType, turnId) => cb.sessionReply(sessionAnchorId(ds), content, msgType, ds.larkAppId, fallbackTurnId(ds, turnId));
|
|
2050
2136
|
setTimeout(async () => {
|
|
2051
|
-
let pendingCardId;
|
|
2052
|
-
let pendingQuoteTargetId;
|
|
2053
2137
|
// Guard: if the user closed the session (or it was torn down for any
|
|
2054
2138
|
// other reason) between attempts, don't post a stale final answer to
|
|
2055
2139
|
// a closed thread.
|
|
@@ -2062,28 +2146,12 @@ function deliverFinalOutput(ds, msg, t, attempt, lockedPendingCardId, lockedQuot
|
|
|
2062
2146
|
// 发表为文档评论(而非飞书卡片),状态卡/占位卡仍留在飞书会话起点。
|
|
2063
2147
|
const docTurn = ds.docCommentTurns?.get(msg.turnId);
|
|
2064
2148
|
if (docTurn) {
|
|
2065
|
-
const loc = localeForBot(ds.larkAppId);
|
|
2066
2149
|
// 嵌套回复到用户那条评论 thread(已挂在其下,无需再 ↪ 前缀)。这是兜底路径
|
|
2067
2150
|
// (模型没显式 botmux send),默认 @ 回原评论人,仅首块加。
|
|
2068
2151
|
const chunks = chunkCommentText(msg.content);
|
|
2069
2152
|
for (let i = 0; i < chunks.length; i++) {
|
|
2070
2153
|
await replyToDocComment(ds.larkAppId, { fileToken: docTurn.fileToken, fileType: docTurn.fileType }, docTurn.commentId, chunks[i], i === 0 ? docTurn.replyToOpenId : undefined);
|
|
2071
2154
|
}
|
|
2072
|
-
// 收尾飞书侧占位卡(streaming-disabled 会话),避免停在「处理中」。
|
|
2073
|
-
// streaming 卡(若开启)会在 idle 自行冻结,无需在此处理。
|
|
2074
|
-
const donePendingId = lockedPendingCardId ?? claimPendingResponseCard(ds.session);
|
|
2075
|
-
if (donePendingId) {
|
|
2076
|
-
try {
|
|
2077
|
-
await updateMessage(ds.larkAppId, donePendingId, buildMarkdownCard(tr('daemon.doc_comment_replied_card', undefined, loc), daemonCardFooterRecipientOpenId(ds, effectiveCliId), resolveBrandLabel(ds.larkAppId), loc));
|
|
2078
|
-
markPendingResponseCardPatchedIfCurrent(ds.session, donePendingId);
|
|
2079
|
-
syncPendingResponseState(ds, ds.session);
|
|
2080
|
-
sessionStore.updateSession(ds.session);
|
|
2081
|
-
}
|
|
2082
|
-
catch (err) {
|
|
2083
|
-
if (!(err instanceof MessageWithdrawnError))
|
|
2084
|
-
logger.warn(`[${t}] failed to finalize 飞书 pending card for doc-comment turn: ${err?.message ?? err}`);
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
2155
|
ds.docCommentTurns?.delete(msg.turnId);
|
|
2088
2156
|
ds.lastBridgeEmittedUuid = msg.lastUuid;
|
|
2089
2157
|
logger.info(`[${t}] doc-comment final_output → posted ${chunks.length} comment(s) on file=${docTurn.fileToken.slice(0, 12)} (turn ${msg.turnId.substring(0, 8)})`);
|
|
@@ -2114,41 +2182,10 @@ function deliverFinalOutput(ds, msg, t, attempt, lockedPendingCardId, lockedQuot
|
|
|
2114
2182
|
locale: localeForBot(ds.larkAppId),
|
|
2115
2183
|
})
|
|
2116
2184
|
: buildMarkdownCard(msg.content, recipientOpenId, resolveBrandLabel(ds.larkAppId), localeForBot(ds.larkAppId));
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
if (ds.session.pendingResponseCardId !== pendingCardId) {
|
|
2122
|
-
await scopedReply(cardJson, 'interactive', msg.turnId);
|
|
2123
|
-
}
|
|
2124
|
-
else {
|
|
2125
|
-
writePendingResponsePatchMarker(ds.session.sessionId, pendingCardId);
|
|
2126
|
-
await updateMessage(ds.larkAppId, pendingCardId, cardJson);
|
|
2127
|
-
markPendingResponsePatchMarkerPatched(ds.session.sessionId);
|
|
2128
|
-
markPendingResponseCardPatchedIfCurrent(ds.session, pendingCardId);
|
|
2129
|
-
syncPendingResponseState(ds, ds.session);
|
|
2130
|
-
sessionStore.updateSession(ds.session);
|
|
2131
|
-
clearPendingResponsePatchMarker(ds.session.sessionId);
|
|
2132
|
-
if (pendingQuoteTargetId && ds.session.lastPatchedResponseCardId === pendingCardId) {
|
|
2133
|
-
addReaction(ds.larkAppId, pendingQuoteTargetId, COMPLETED_REACTION_EMOJI_TYPE)
|
|
2134
|
-
.catch((err) => logger.warn(`[${t}] failed to add completion reaction to ${pendingQuoteTargetId}: ${err?.message ?? err}`));
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
catch (err) {
|
|
2139
|
-
clearPendingResponsePatchMarker(ds.session.sessionId);
|
|
2140
|
-
if (!(err instanceof MessageWithdrawnError))
|
|
2141
|
-
throw err;
|
|
2142
|
-
logger.warn(`[${t}] Pending response card withdrawn while forwarding final_output; sending a new reply`);
|
|
2143
|
-
await scopedReply(cardJson, 'interactive', msg.turnId);
|
|
2144
|
-
markPendingResponseCardPatchedIfCurrent(ds.session, pendingCardId);
|
|
2145
|
-
syncPendingResponseState(ds, ds.session);
|
|
2146
|
-
sessionStore.updateSession(ds.session);
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
else {
|
|
2150
|
-
await scopedReply(cardJson, 'interactive', msg.turnId);
|
|
2151
|
-
}
|
|
2185
|
+
// Always deliver the answer as a fresh message — never PATCH a card in
|
|
2186
|
+
// place. message.patch is silent (no Feishu notification / unread), which
|
|
2187
|
+
// used to swallow the answer; a brand-new message always pings.
|
|
2188
|
+
await scopedReply(cardJson, 'interactive', msg.turnId);
|
|
2152
2189
|
ds.lastBridgeEmittedUuid = msg.lastUuid;
|
|
2153
2190
|
logger.info(`[${t}] Bridge final_output forwarded (turn ${msg.turnId.substring(0, 8)}, ${msg.content.length} chars, kind=${msg.kind ?? 'bridge'}, attempt ${attempt + 1})`);
|
|
2154
2191
|
}
|
|
@@ -2161,8 +2198,6 @@ function deliverFinalOutput(ds, msg, t, attempt, lockedPendingCardId, lockedQuot
|
|
|
2161
2198
|
cb.closeSession(ds);
|
|
2162
2199
|
return;
|
|
2163
2200
|
}
|
|
2164
|
-
if (pendingCardId)
|
|
2165
|
-
clearPendingResponsePatchMarker(ds.session.sessionId);
|
|
2166
2201
|
const next = attempt + 1;
|
|
2167
2202
|
if (next >= FINAL_OUTPUT_RETRY_BACKOFF_MS.length) {
|
|
2168
2203
|
logger.error(`[${t}] Bridge final_output gave up after ${next} attempts (turn ${msg.turnId.substring(0, 8)}): ${err.message}`);
|
|
@@ -2171,7 +2206,7 @@ function deliverFinalOutput(ds, msg, t, attempt, lockedPendingCardId, lockedQuot
|
|
|
2171
2206
|
return;
|
|
2172
2207
|
}
|
|
2173
2208
|
logger.warn(`[${t}] Bridge final_output attempt ${next} failed (${err.message}); retrying in ${FINAL_OUTPUT_RETRY_BACKOFF_MS[next]}ms`);
|
|
2174
|
-
deliverFinalOutput(ds, msg, t, next
|
|
2209
|
+
deliverFinalOutput(ds, msg, t, next);
|
|
2175
2210
|
}
|
|
2176
2211
|
}, FINAL_OUTPUT_RETRY_BACKOFF_MS[attempt] ?? 0);
|
|
2177
2212
|
}
|
|
@@ -2179,6 +2214,7 @@ function deliverFinalOutput(ds, msg, t, attempt, lockedPendingCardId, lockedQuot
|
|
|
2179
2214
|
* fork. Intentionally underscored to discourage non-test callers. */
|
|
2180
2215
|
export const __testOnly_deliverFinalOutput = deliverFinalOutput;
|
|
2181
2216
|
export const __testOnly_setupWorkerHandlers = setupWorkerHandlers;
|
|
2217
|
+
export const __testOnly_finishTurnReactions = finishTurnReactions;
|
|
2182
2218
|
// ─── Fork adopt worker ──────────────────────────────────────────────────────
|
|
2183
2219
|
export function forkAdoptWorker(ds, opts) {
|
|
2184
2220
|
const cb = requireCallbacks();
|