botmux 3.0.0 → 3.1.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/dist/adapters/backend/herdr-backend.d.ts +9 -7
- package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
- package/dist/adapters/backend/herdr-backend.js +63 -53
- package/dist/adapters/backend/herdr-backend.js.map +1 -1
- package/dist/adapters/backend/riff-backend.d.ts +6 -1
- package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
- package/dist/adapters/backend/riff-backend.js +7 -2
- package/dist/adapters/backend/riff-backend.js.map +1 -1
- package/dist/adapters/backend/sandbox.d.ts +20 -16
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +193 -68
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts +39 -2
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +66 -6
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +2 -2
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
- package/dist/adapters/backend/zellij-backend.js +9 -3
- package/dist/adapters/backend/zellij-backend.js.map +1 -1
- package/dist/adapters/cli/claude-code.d.ts +25 -0
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +79 -9
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/read-isolation.d.ts +2 -1
- package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
- package/dist/adapters/cli/read-isolation.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +2 -2
- package/dist/adapters/hook-installer.d.ts +11 -0
- package/dist/adapters/hook-installer.d.ts.map +1 -1
- package/dist/adapters/hook-installer.js +72 -11
- package/dist/adapters/hook-installer.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -0
- package/dist/config.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +354 -143
- 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 +12 -5
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dashboard-rows.d.ts +5 -0
- package/dist/core/dashboard-rows.d.ts.map +1 -1
- package/dist/core/dashboard-rows.js +2 -0
- package/dist/core/dashboard-rows.js.map +1 -1
- package/dist/core/doc-comment-prompt.d.ts +2 -0
- package/dist/core/doc-comment-prompt.d.ts.map +1 -1
- package/dist/core/doc-comment-prompt.js +30 -0
- package/dist/core/doc-comment-prompt.js.map +1 -1
- package/dist/core/github-auth.d.ts +8 -0
- package/dist/core/github-auth.d.ts.map +1 -0
- package/dist/core/github-auth.js +44 -0
- package/dist/core/github-auth.js.map +1 -0
- package/dist/core/managed-origin-capability.d.ts +7 -0
- package/dist/core/managed-origin-capability.d.ts.map +1 -1
- package/dist/core/managed-origin-capability.js +12 -0
- package/dist/core/managed-origin-capability.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/restart-report.d.ts +7 -1
- package/dist/core/restart-report.d.ts.map +1 -1
- package/dist/core/restart-report.js +19 -5
- package/dist/core/restart-report.js.map +1 -1
- package/dist/core/session-activity.d.ts +8 -0
- package/dist/core/session-activity.d.ts.map +1 -1
- package/dist/core/session-activity.js +16 -0
- package/dist/core/session-activity.js.map +1 -1
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +4 -2
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-marker.d.ts.map +1 -1
- package/dist/core/session-marker.js +5 -2
- package/dist/core/session-marker.js.map +1 -1
- package/dist/core/types.d.ts +48 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +55 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/update-check.d.ts +11 -0
- package/dist/core/update-check.d.ts.map +1 -1
- package/dist/core/update-check.js +6 -1
- package/dist/core/update-check.js.map +1 -1
- package/dist/core/vc-meeting-prepare-command.d.ts +22 -0
- package/dist/core/vc-meeting-prepare-command.d.ts.map +1 -0
- package/dist/core/vc-meeting-prepare-command.js +50 -0
- package/dist/core/vc-meeting-prepare-command.js.map +1 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +21 -10
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +15 -5
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +645 -50
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +6 -1
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +34 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/preferences.d.ts +1 -1
- package/dist/dashboard/web/preferences.d.ts.map +1 -1
- package/dist/dashboard/web/preferences.js +1 -1
- package/dist/dashboard/web/preferences.js.map +1 -1
- package/dist/dashboard/web/sessions-page.d.ts +19 -0
- package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-page.js +66 -7
- package/dist/dashboard/web/sessions-page.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts +31 -0
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +82 -0
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/{bot-defaults-page-MBORWWP7.js → bot-defaults-page-KLDCZNLP.js} +5 -5
- package/dist/dashboard-web/chunks/{chunk-G43RUPUN.js → chunk-EZAW2EPM.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-EIO5AWDA.js → chunk-FMJNOATE.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-4QTBR7UF.js → chunk-KEAOTUL3.js} +7 -7
- package/dist/dashboard-web/chunks/{chunk-7STGZ5BX.js → chunk-MLNI5BKE.js} +1 -1
- package/dist/dashboard-web/chunks/{connectors-page-LKDT4WBG.js → connectors-page-O32LFAL5.js} +1 -1
- package/dist/dashboard-web/chunks/{groups-page-XKIWJ2TP.js → groups-page-PSAFIZ6M.js} +1 -1
- package/dist/dashboard-web/chunks/{insights-page-CBEZCYKV.js → insights-page-V23LLATX.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-AQTHAWXA.js → monitor-room-7X5AKYOF.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-2R4NCNBQ.js → monitoring-page-CBA3CQYL.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-5M6MQIIL.js → office-page-RL6QSEM7.js} +1 -1
- package/dist/dashboard-web/chunks/{overview-page-N22HDZ2V.js → overview-page-XQIPO6KH.js} +1 -1
- package/dist/dashboard-web/chunks/{roles-page-J6K2HWFX.js → roles-page-ACIWV7QS.js} +1 -1
- package/dist/dashboard-web/chunks/{schedules-page-CGAQCB4J.js → schedules-page-KJDUKOJV.js} +1 -1
- package/dist/dashboard-web/chunks/sessions-page-2WDWHSP4.js +3 -0
- package/dist/dashboard-web/chunks/{settings-page-GLRWV5KU.js → settings-page-6VX4OUGG.js} +1 -1
- package/dist/dashboard-web/chunks/{skills-page-KQVW42QU.js → skills-page-WXE3UYLT.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-5PYUOV5M.js → team-federation-page-PYTF4FLM.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-DXPFILRZ.js → v3-page-G6CD6O3I.js} +1 -1
- package/dist/dashboard-web/chunks/{whiteboards-page-NUH6VZ4I.js → whiteboards-page-6SG33GP4.js} +1 -1
- package/dist/dashboard-web/style.css +167 -2
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +19 -3
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +19 -3
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +11 -2
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +2 -0
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +219 -139
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +4 -0
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +249 -5
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/forward-followup-buffer.d.ts +26 -0
- package/dist/im/lark/forward-followup-buffer.d.ts.map +1 -0
- package/dist/im/lark/forward-followup-buffer.js +48 -0
- package/dist/im/lark/forward-followup-buffer.js.map +1 -0
- package/dist/im/lark/forward-followup-content.d.ts +6 -0
- package/dist/im/lark/forward-followup-content.d.ts.map +1 -0
- package/dist/im/lark/forward-followup-content.js +46 -0
- package/dist/im/lark/forward-followup-content.js.map +1 -0
- package/dist/im/lark/forward-followup-store.d.ts +9 -0
- package/dist/im/lark/forward-followup-store.d.ts.map +1 -0
- package/dist/im/lark/forward-followup-store.js +48 -0
- package/dist/im/lark/forward-followup-store.js.map +1 -0
- package/dist/im/lark/message-parser.d.ts +1 -1
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +1 -2
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/platform/tunnel-client.js +3 -3
- package/dist/platform/tunnel-client.js.map +1 -1
- package/dist/services/local-cli-opener.d.ts +7 -0
- package/dist/services/local-cli-opener.d.ts.map +1 -1
- package/dist/services/local-cli-opener.js +111 -3
- package/dist/services/local-cli-opener.js.map +1 -1
- package/dist/services/skill-registry-store.d.ts.map +1 -1
- package/dist/services/skill-registry-store.js +15 -2
- package/dist/services/skill-registry-store.js.map +1 -1
- package/dist/services/vc-meeting-consumer-isolation.d.ts +7 -5
- package/dist/services/vc-meeting-consumer-isolation.d.ts.map +1 -1
- package/dist/services/vc-meeting-consumer-isolation.js +7 -5
- package/dist/services/vc-meeting-consumer-isolation.js.map +1 -1
- package/dist/services/vc-meeting-preparations-store.d.ts +22 -0
- package/dist/services/vc-meeting-preparations-store.d.ts.map +1 -0
- package/dist/services/vc-meeting-preparations-store.js +131 -0
- package/dist/services/vc-meeting-preparations-store.js.map +1 -0
- package/dist/services/vc-meeting-runtime-store.d.ts +9 -0
- package/dist/services/vc-meeting-runtime-store.d.ts.map +1 -1
- package/dist/services/vc-meeting-runtime-store.js +16 -0
- package/dist/services/vc-meeting-runtime-store.js.map +1 -1
- package/dist/setup/ensure-tmux.d.ts.map +1 -1
- package/dist/setup/ensure-tmux.js +9 -11
- package/dist/setup/ensure-tmux.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/child-env.d.ts +24 -8
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +49 -8
- package/dist/utils/child-env.js.map +1 -1
- package/dist/utils/daemon-discovery.d.ts +8 -0
- package/dist/utils/daemon-discovery.d.ts.map +1 -1
- package/dist/utils/daemon-discovery.js +19 -0
- package/dist/utils/daemon-discovery.js.map +1 -1
- package/dist/utils/ready-gate.d.ts +9 -3
- package/dist/utils/ready-gate.d.ts.map +1 -1
- package/dist/utils/ready-gate.js +12 -4
- package/dist/utils/ready-gate.js.map +1 -1
- package/dist/worker.js +141 -29
- package/dist/worker.js.map +1 -1
- package/dist/workflows/ops-projection.d.ts.map +1 -1
- package/dist/workflows/ops-projection.js +27 -5
- package/dist/workflows/ops-projection.js.map +1 -1
- package/dist/workflows/v3/distillation-runner.d.ts +4 -0
- package/dist/workflows/v3/distillation-runner.d.ts.map +1 -1
- package/dist/workflows/v3/distillation-runner.js +4 -2
- package/dist/workflows/v3/distillation-runner.js.map +1 -1
- package/package.json +1 -1
- package/dist/dashboard-web/chunks/sessions-page-WJ2QYTIE.js +0 -3
|
@@ -19,13 +19,13 @@ import { buildRepoSelectCard, buildAdoptSelectCard, buildCodexAppThreadSelectCar
|
|
|
19
19
|
import { computeSandboxDiff } from '../services/sandbox-land.js';
|
|
20
20
|
import { handleDashboardCommand } from './dashboard-command/index.js';
|
|
21
21
|
import { createCliAdapterSync } from '../adapters/cli/registry.js';
|
|
22
|
-
import { deleteMessage, sendMessage, sendUserMessage, listChatBotMembers, resolveUserUnionId, getChatModeStrict, uploadFile, UserTokenMissingError } from '../im/lark/client.js';
|
|
22
|
+
import { deleteMessage, sendMessage, sendUserMessage, replyMessage, listChatBotMembers, resolveUserUnionId, getChatModeStrict, uploadFile, UserTokenMissingError } from '../im/lark/client.js';
|
|
23
23
|
import { chatAppLink, normalizeBrand } from '../im/lark/lark-hosts.js';
|
|
24
24
|
import { claimPairing } from '../services/pairing-store.js';
|
|
25
25
|
import { logger } from '../utils/logger.js';
|
|
26
26
|
import { scheduleTimeZone } from '../utils/timezone.js';
|
|
27
|
-
import { killWorker, forkWorker, forkAdoptWorker, getCurrentCliVersion, postFreshStreamingCard, postPrivateSnapshotCard, resolvePrivateCardAudience, deliverEphemeralOrReply, deliverWritableTerminalCardTo } from './worker-pool.js';
|
|
28
|
-
import { expandHome, getSessionWorkingDir, getProjectScanDirs, rememberLastCliInput } from './session-manager.js';
|
|
27
|
+
import { killWorker, suspendWorker, forkWorker, forkAdoptWorker, getCurrentCliVersion, postFreshStreamingCard, postPrivateSnapshotCard, resolvePrivateCardAudience, deliverEphemeralOrReply, deliverWritableTerminalCardTo } from './worker-pool.js';
|
|
28
|
+
import { expandHome, getSessionWorkingDir, getProjectScanDirs, rememberLastCliInput, buildNewTopicCliInput, ensureSessionWhiteboard, getAvailableBots, } from './session-manager.js';
|
|
29
29
|
import { discoverSlashCommandsForAdapter, listMcpServerNames, supportsFilesystemCommandDiscovery } from './command-discovery.js';
|
|
30
30
|
import { validateWorkingDir } from './working-dir.js';
|
|
31
31
|
import { repinSessionWorkingDir } from './session-cwd.js';
|
|
@@ -35,8 +35,10 @@ import { listCodexAppThreads } from '../services/codex-app-threads.js';
|
|
|
35
35
|
import { generateAuthUrl, getTokenStatus, resolveUserToken, DOC_COMMENT_OAUTH_SCOPES } from '../utils/user-token.js';
|
|
36
36
|
import { DocSubscriptionPermissionError, listDocComments, resolveDocFile, subscribeDocFile, unsubscribeDocFile } from '../im/lark/doc-comment.js';
|
|
37
37
|
import { parseDocWatchCommand } from './doc-watch-command.js';
|
|
38
|
+
import { parseVcMeetingPrepareCommand } from './vc-meeting-prepare-command.js';
|
|
38
39
|
import { latestDocCommentPollCursor } from './doc-comment-poller.js';
|
|
39
40
|
import { putDocSubscription, removeDocSubscription, listDocSubscriptionsForSession, listAllDocSubscriptions, getDocSubscription, } from '../services/doc-subs-store.js';
|
|
41
|
+
import { findVcMeetingPreparationByChat, getVcMeetingPreparation, listVcMeetingPreparations, putVcMeetingPreparation, removeVcMeetingPreparation, removeVcMeetingPreparationsByChat, } from '../services/vc-meeting-preparations-store.js';
|
|
40
42
|
import { bindOncall, unbindOncall, getOncallStatus } from '../services/oncall-store.js';
|
|
41
43
|
import { CONFIG_FIELDS, findConfigField, settableFieldKeys, parseBooleanValue, applyConfigField, setBotAllowedUsers, getConfigSnapshot, getConfigCardData, coerceConfigValue, } from '../services/bot-config-store.js';
|
|
42
44
|
import { resolveCliId, findInvalidAllowedUserEntries } from '../setup/bot-config-editor.js';
|
|
@@ -50,7 +52,7 @@ import { invalidWorkingDirs } from '../utils/working-dir.js';
|
|
|
50
52
|
import { writeRoleFile, deleteRoleFile, resolveRole, resolveRoleFile, resolveTeamRoleFile, writeTeamRoleFile, deleteTeamRoleFile, MAX_ROLE_BYTES } from './role-resolver.js';
|
|
51
53
|
import { getBotCapability, setBotCapability, clearBotCapability } from '../services/bot-profile-store.js';
|
|
52
54
|
import { deleteRoleProfileEntry, deleteRoleProfileIfEmpty, isValidRoleProfileId, listRoleProfileEntries, listRoleProfiles, MAX_ROLE_PROFILE_ENTRY_BYTES, readRoleProfileEntry, writeRoleProfileEntry, } from '../services/role-profile-store.js';
|
|
53
|
-
import { sessionKey, sessionAnchorId } from './types.js';
|
|
55
|
+
import { sessionKey, sessionAnchorId, markRepoCardConsumed, claimCurrentRepoCard } from './types.js';
|
|
54
56
|
import { t, localeForBot } from '../i18n/index.js';
|
|
55
57
|
import { runSkillsImCommand } from './skills/im-command.js';
|
|
56
58
|
import { fetchDaemonIpc } from './daemon-ipc-auth.js';
|
|
@@ -1321,7 +1323,15 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1321
1323
|
break;
|
|
1322
1324
|
}
|
|
1323
1325
|
const resolvedPath = validation.resolvedPath;
|
|
1324
|
-
|
|
1326
|
+
// Persistent backends can cold-resume without treating a cwd switch as
|
|
1327
|
+
// a real session close. This also preserves a sandbox upper long enough
|
|
1328
|
+
// for the next worker to relocate Claude's cwd-scoped transcript.
|
|
1329
|
+
// Adopted sessions are observers of a user-owned CLI and keep the old
|
|
1330
|
+
// detach/kill behavior; PTY falls back to killWorker, while its normal
|
|
1331
|
+
// on-disk transcript remains available for the resume-sync preflight.
|
|
1332
|
+
const suspended = !ds.adoptedFrom && suspendWorker(ds, 'working_dir_changed');
|
|
1333
|
+
if (!suspended)
|
|
1334
|
+
killWorker(ds);
|
|
1325
1335
|
repinSessionWorkingDir(ds, resolvedPath);
|
|
1326
1336
|
if (validation.created) {
|
|
1327
1337
|
await sessionReply(rootId, t('cmd.cd.created_switched', { path: resolvedPath }, loc));
|
|
@@ -1371,29 +1381,43 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1371
1381
|
// CLI in whatever workingDir is currently set on the session. Shared by
|
|
1372
1382
|
// `commitRepoSelection` (a repo was named) and the bare-`/repo` launch
|
|
1373
1383
|
// (use the default workingDir) — both only run while `pendingRepo`.
|
|
1374
|
-
const forkPendingCli = async (replyText) => {
|
|
1384
|
+
const forkPendingCli = async (replyText, claimAlready = false) => {
|
|
1385
|
+
if (!claimAlready) {
|
|
1386
|
+
if (ds.pendingRepoCommitInFlight) {
|
|
1387
|
+
await sessionReply(rootId, t('cmd.repo.worktree_in_progress', undefined, loc));
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
ds.pendingRepoCommitInFlight = true;
|
|
1391
|
+
}
|
|
1375
1392
|
const selfBot = getBot(ds.larkAppId);
|
|
1376
1393
|
const botCfg = selfBot.config;
|
|
1377
1394
|
const commitGenSessionId = ds.session.sessionId;
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
+
let committed = false;
|
|
1396
|
+
try {
|
|
1397
|
+
// Keep pendingRepo=true while prompt context is awaited. Incoming
|
|
1398
|
+
// messages stay in the same buffer and card selections see the
|
|
1399
|
+
// shared in-flight claim instead of treating this as a repo switch.
|
|
1400
|
+
const initiallyBuffered = (ds.pendingPrompt?.trim().length ?? 0) > 0 ||
|
|
1401
|
+
(ds.pendingAttachments?.length ?? 0) > 0 ||
|
|
1402
|
+
(ds.pendingFollowUps?.length ?? 0) > 0;
|
|
1403
|
+
const availableBots = initiallyBuffered
|
|
1404
|
+
? await getAvailableBots(ds.larkAppId, ds.chatId)
|
|
1405
|
+
: [];
|
|
1406
|
+
const stillActive = activeSessions.get(sessionKey(rootId, larkAppId)) === ds;
|
|
1407
|
+
if (!stillActive || ds.session.sessionId !== commitGenSessionId ||
|
|
1408
|
+
!ds.pendingRepo || (ds.worker && !ds.worker.killed)) {
|
|
1409
|
+
logger.warn(`[${logTag}] Session changed while preparing the pending CLI (${commitGenSessionId} → ${ds.session.sessionId}, active=${stillActive}, pending=${!!ds.pendingRepo}, worker=${!!ds.worker})`);
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
const pendingPrompt = ds.pendingPrompt ?? '';
|
|
1413
|
+
const pendingRawInput = ds.pendingRawInput;
|
|
1414
|
+
const hasBufferedInput = pendingPrompt.trim().length > 0 ||
|
|
1415
|
+
(ds.pendingAttachments?.length ?? 0) > 0 ||
|
|
1416
|
+
(ds.pendingFollowUps?.length ?? 0) > 0;
|
|
1417
|
+
if (hasBufferedInput)
|
|
1395
1418
|
ensureSessionWhiteboard(ds);
|
|
1396
|
-
|
|
1419
|
+
const wrappedInput = hasBufferedInput
|
|
1420
|
+
? buildNewTopicCliInput(pendingPrompt, ds.session.sessionId, ds.session.cliId ?? botCfg.cliId, ds.session.cliPathOverride ?? botCfg.cliPathOverride, ds.pendingAttachments, ds.pendingMentions, availableBots, ds.pendingFollowUps, { name: selfBot.botName, openId: selfBot.botOpenId }, loc, ds.pendingSender, {
|
|
1397
1421
|
larkAppId,
|
|
1398
1422
|
chatId: ds.chatId,
|
|
1399
1423
|
whiteboardId: ds.session.whiteboardId,
|
|
@@ -1403,64 +1427,82 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1403
1427
|
codexAppMessageContext: ds.pendingCodexAppMessageContext,
|
|
1404
1428
|
codexAppFollowUps: ds.pendingCodexAppFollowUps,
|
|
1405
1429
|
codexAppFollowUpContexts: ds.pendingCodexAppFollowUpContexts,
|
|
1406
|
-
})
|
|
1430
|
+
})
|
|
1431
|
+
: { content: '' };
|
|
1432
|
+
const pendingTurnId = ds.pendingTurnId;
|
|
1433
|
+
const previousPendingFollowUpInput = ds.pendingFollowUpInput;
|
|
1434
|
+
if (pendingRawInput && hasBufferedInput) {
|
|
1407
1435
|
ds.pendingFollowUpInput = {
|
|
1408
1436
|
userPrompt: ds.pendingCodexAppText !== undefined || ds.pendingCodexAppFollowUps
|
|
1409
1437
|
? [ds.pendingCodexAppText ?? '', ...(ds.pendingCodexAppFollowUps ?? [])].filter(Boolean).join('\n\n')
|
|
1410
1438
|
: pendingPrompt || ds.pendingFollowUps?.join('\n\n') || '',
|
|
1411
|
-
cliInput:
|
|
1412
|
-
...(
|
|
1413
|
-
|
|
1439
|
+
cliInput: wrappedInput.content,
|
|
1440
|
+
...(ds.pendingFollowUpTurnId ? { turnId: ds.pendingFollowUpTurnId } : {}),
|
|
1441
|
+
...((ds.session.cliId ?? botCfg.cliId) === 'codex-app' && botCfg.codexAppCleanInput === true && wrappedInput.codexAppInput
|
|
1442
|
+
? { codexAppInput: wrappedInput.codexAppInput }
|
|
1414
1443
|
: {}),
|
|
1415
1444
|
codexAppInputGateFrozen: true,
|
|
1416
1445
|
};
|
|
1417
1446
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1447
|
+
ds.pendingRepo = false;
|
|
1448
|
+
publishAttentionPatch(ds);
|
|
1449
|
+
try {
|
|
1450
|
+
if (pendingRawInput)
|
|
1451
|
+
forkWorker(ds, '', false);
|
|
1452
|
+
else if (pendingTurnId && hasBufferedInput)
|
|
1453
|
+
forkWorker(ds, wrappedInput, { turnId: pendingTurnId });
|
|
1454
|
+
else if (hasBufferedInput)
|
|
1455
|
+
forkWorker(ds, wrappedInput);
|
|
1456
|
+
else
|
|
1457
|
+
forkWorker(ds, '', false);
|
|
1458
|
+
}
|
|
1459
|
+
catch (e) {
|
|
1460
|
+
ds.pendingRepo = true;
|
|
1461
|
+
ds.pendingFollowUpInput = previousPendingFollowUpInput;
|
|
1462
|
+
publishAttentionPatch(ds);
|
|
1463
|
+
throw e;
|
|
1464
|
+
}
|
|
1465
|
+
if (pendingRawInput || hasBufferedInput) {
|
|
1466
|
+
rememberLastCliInput(ds, pendingRawInput ?? pendingPrompt, pendingRawInput ?? wrappedInput);
|
|
1467
|
+
}
|
|
1468
|
+
ds.pendingPrompt = undefined;
|
|
1469
|
+
ds.pendingCodexAppText = undefined;
|
|
1470
|
+
ds.pendingCodexAppApplicationContext = undefined;
|
|
1471
|
+
ds.pendingCodexAppMessageContext = undefined;
|
|
1472
|
+
ds.pendingAttachments = undefined;
|
|
1473
|
+
ds.pendingMentions = undefined;
|
|
1474
|
+
ds.pendingSubstituteTrigger = undefined;
|
|
1475
|
+
ds.pendingSender = undefined;
|
|
1476
|
+
ds.pendingFollowUps = undefined;
|
|
1477
|
+
ds.pendingCodexAppFollowUps = undefined;
|
|
1478
|
+
ds.pendingCodexAppFollowUpContexts = undefined;
|
|
1479
|
+
ds.pendingFollowUpTurnId = undefined;
|
|
1480
|
+
ds.pendingTurnId = undefined;
|
|
1481
|
+
committed = true;
|
|
1482
|
+
// Local one-shot consume BEFORE network awaits. Feishu withdraw is
|
|
1483
|
+
// best-effort; stale card clicks must be rejected via this mark even
|
|
1484
|
+
// if sessionReply throws or deleteMessage lags/fails.
|
|
1485
|
+
const cardToWithdraw = ds.repoCardMessageId;
|
|
1486
|
+
markRepoCardConsumed(ds, cardToWithdraw);
|
|
1487
|
+
ds.repoCardMessageId = undefined;
|
|
1488
|
+
// Hold the claim through confirmation + best-effort card withdraw.
|
|
1489
|
+
try {
|
|
1490
|
+
await sessionReply(rootId, replyText);
|
|
1491
|
+
}
|
|
1492
|
+
catch (e) {
|
|
1493
|
+
logger.warn(`[${logTag}] Confirm reply after pending repo commit failed: ${e instanceof Error ? e.message : e}`);
|
|
1494
|
+
}
|
|
1495
|
+
if (cardToWithdraw) {
|
|
1496
|
+
try {
|
|
1497
|
+
await deleteMessage(ds.larkAppId, cardToWithdraw);
|
|
1498
|
+
}
|
|
1499
|
+
catch { /* best-effort */ }
|
|
1443
1500
|
}
|
|
1444
|
-
rememberLastCliInput(ds, pendingPrompt, prompt);
|
|
1445
|
-
forkWorker(ds, prompt);
|
|
1446
1501
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
// anything (see worker.ts: the init prompt is only queued when truthy).
|
|
1450
|
-
forkWorker(ds, '', false);
|
|
1502
|
+
finally {
|
|
1503
|
+
ds.pendingRepoCommitInFlight = false;
|
|
1451
1504
|
}
|
|
1452
|
-
|
|
1453
|
-
ds.pendingCodexAppText = undefined;
|
|
1454
|
-
ds.pendingCodexAppApplicationContext = undefined;
|
|
1455
|
-
ds.pendingCodexAppMessageContext = undefined;
|
|
1456
|
-
ds.pendingAttachments = undefined;
|
|
1457
|
-
ds.pendingMentions = undefined;
|
|
1458
|
-
ds.pendingSubstituteTrigger = undefined;
|
|
1459
|
-
ds.pendingSender = undefined;
|
|
1460
|
-
ds.pendingFollowUps = undefined;
|
|
1461
|
-
ds.pendingCodexAppFollowUps = undefined;
|
|
1462
|
-
ds.pendingCodexAppFollowUpContexts = undefined;
|
|
1463
|
-
await sessionReply(rootId, replyText);
|
|
1505
|
+
return committed;
|
|
1464
1506
|
};
|
|
1465
1507
|
// Shared commit path for an already-resolved repo: update the session's
|
|
1466
1508
|
// working dir, then either fork into the pending CLI (first spawn) or
|
|
@@ -1468,13 +1510,27 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1468
1510
|
// numeric `/repo <N>` form and the `/repo <path|name>` form.
|
|
1469
1511
|
const commitRepoSelection = async (selectedPath, displayName, how) => {
|
|
1470
1512
|
if (ds.pendingRepo) {
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
ds.
|
|
1476
|
-
|
|
1477
|
-
|
|
1513
|
+
if (ds.pendingRepoCommitInFlight) {
|
|
1514
|
+
await sessionReply(rootId, t('cmd.repo.worktree_in_progress', undefined, loc));
|
|
1515
|
+
return false;
|
|
1516
|
+
}
|
|
1517
|
+
ds.pendingRepoCommitInFlight = true;
|
|
1518
|
+
try {
|
|
1519
|
+
// Claim before changing cwd so a card selection cannot overwrite
|
|
1520
|
+
// this choice while the pending prompt is prepared.
|
|
1521
|
+
ds.workingDir = selectedPath;
|
|
1522
|
+
ds.session.workingDir = selectedPath;
|
|
1523
|
+
ds.session.riffRepoDirs = undefined;
|
|
1524
|
+
sessionStore.updateSession(ds.session);
|
|
1525
|
+
// forkPendingCli owns claim release + confirm reply + card withdraw.
|
|
1526
|
+
if (!await forkPendingCli(t('cmd.repo.selected_in_pending', { name: displayName }, loc), true)) {
|
|
1527
|
+
return false;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
catch (e) {
|
|
1531
|
+
ds.pendingRepoCommitInFlight = false;
|
|
1532
|
+
throw e;
|
|
1533
|
+
}
|
|
1478
1534
|
}
|
|
1479
1535
|
else {
|
|
1480
1536
|
// Safety net: a mid-session `/repo` switch closes the running
|
|
@@ -1488,11 +1544,15 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1488
1544
|
// anchor — expected; `/close` the new one first, or use the
|
|
1489
1545
|
// command.) Mirrors the `/close` case above.
|
|
1490
1546
|
//
|
|
1547
|
+
// Claim any open repo card BEFORE killWorker / await so a concurrent
|
|
1548
|
+
// card click cannot double-switch while this text path runs.
|
|
1549
|
+
//
|
|
1491
1550
|
// The new cwd is NOT written onto the old session here — it would
|
|
1492
1551
|
// pollute the displaced session's stored workingDir (and the closed
|
|
1493
1552
|
// card), so `claude --resume` later would reopen the old context in
|
|
1494
1553
|
// the new repo's cwd. The new repo is pinned onto the fresh session
|
|
1495
1554
|
// below instead.
|
|
1555
|
+
const claimedCard = claimCurrentRepoCard(ds, undefined);
|
|
1496
1556
|
const closedCard = buildClosedSessionCard(ds, loc);
|
|
1497
1557
|
killWorker(ds);
|
|
1498
1558
|
sessionStore.closeSession(ds.session.sessionId);
|
|
@@ -1512,13 +1572,21 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1512
1572
|
sessionStore.updateSession(ds.session);
|
|
1513
1573
|
ds.hasHistory = false;
|
|
1514
1574
|
forkWorker(ds, '', false);
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1575
|
+
try {
|
|
1576
|
+
await sessionReply(rootId, t('cmd.repo.switched_to', { name: displayName }, loc));
|
|
1577
|
+
}
|
|
1578
|
+
catch (e) {
|
|
1579
|
+
logger.warn(`[${logTag}] Confirm reply after mid-session repo switch failed: ${e instanceof Error ? e.message : e}`);
|
|
1580
|
+
}
|
|
1581
|
+
if (claimedCard) {
|
|
1582
|
+
try {
|
|
1583
|
+
await deleteMessage(ds.larkAppId, claimedCard);
|
|
1584
|
+
}
|
|
1585
|
+
catch { /* best-effort */ }
|
|
1586
|
+
}
|
|
1520
1587
|
}
|
|
1521
1588
|
logger.info(`[${logTag}] Repo selected via ${how}: ${selectedPath}`);
|
|
1589
|
+
return true;
|
|
1522
1590
|
};
|
|
1523
1591
|
// `/repo wt <N|name|path> [branch]` → create a worktree off the repo's
|
|
1524
1592
|
// remote default branch and open THAT as the session repo. Without a
|
|
@@ -1553,7 +1621,7 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1553
1621
|
}
|
|
1554
1622
|
repoPath = resolved.path;
|
|
1555
1623
|
}
|
|
1556
|
-
if (ds.worktreeCreating) {
|
|
1624
|
+
if (ds.worktreeCreating || ds.pendingRepoCommitInFlight) {
|
|
1557
1625
|
await sessionReply(rootId, t('cmd.repo.worktree_in_progress', undefined, loc));
|
|
1558
1626
|
break;
|
|
1559
1627
|
}
|
|
@@ -1634,7 +1702,7 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1634
1702
|
// would double-fork. One lock gates both kinds until the commit
|
|
1635
1703
|
// settles. (Bare `/repo` without pending only posts the picker card —
|
|
1636
1704
|
// harmless, so it stays open.)
|
|
1637
|
-
if (ds?.worktreeCreating && (repoArg || ds
|
|
1705
|
+
if ((ds?.worktreeCreating || ds?.pendingRepoCommitInFlight) && (repoArg || ds?.pendingRepo)) {
|
|
1638
1706
|
await sessionReply(rootId, t('cmd.repo.worktree_in_progress', undefined, loc));
|
|
1639
1707
|
break;
|
|
1640
1708
|
}
|
|
@@ -1680,11 +1748,10 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
1680
1748
|
break;
|
|
1681
1749
|
}
|
|
1682
1750
|
const cwd = getSessionWorkingDir(ds);
|
|
1751
|
+
// bare /repo is the text twin of skip_repo: launch in the default
|
|
1752
|
+
// cwd without pinning it (forkPendingCli does not write workingDir).
|
|
1753
|
+
// Confirmation + card withdraw run under the claim inside forkPendingCli.
|
|
1683
1754
|
await forkPendingCli(t('cmd.skip.opened', { cwd }, loc));
|
|
1684
|
-
if (ds.repoCardMessageId) {
|
|
1685
|
-
deleteMessage(ds.larkAppId, ds.repoCardMessageId);
|
|
1686
|
-
ds.repoCardMessageId = undefined;
|
|
1687
|
-
}
|
|
1688
1755
|
logger.info(`[${logTag}] Bare /repo while pending → launch in workingDir ${cwd}`);
|
|
1689
1756
|
break;
|
|
1690
1757
|
}
|
|
@@ -2055,7 +2122,12 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
2055
2122
|
const mode = request.requestedMode
|
|
2056
2123
|
?? (botCfg.docSubscribeDefaultMode === 'all' ? 'all' : 'mention-only');
|
|
2057
2124
|
const anchor = ds ? sessionAnchorId(ds) : `doc:${file.fileToken}`;
|
|
2058
|
-
|
|
2125
|
+
// Existing chat/thread sessions own their project binding. A watch
|
|
2126
|
+
// without an explicit --dir inherits that binding; session-less
|
|
2127
|
+
// document watches keep their own stored/mapped directory fallback.
|
|
2128
|
+
const effectiveDir = ds
|
|
2129
|
+
? (validatedDir ?? ds.workingDir ?? ds.session.workingDir)
|
|
2130
|
+
: (validatedDir ?? existing?.workingDir ?? botCfg.docRepoMap?.[file.fileToken]);
|
|
2059
2131
|
let pollCursorAt;
|
|
2060
2132
|
let pollCursorReplyId;
|
|
2061
2133
|
let pollBaselineReady;
|
|
@@ -2106,6 +2178,8 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
2106
2178
|
}, loc);
|
|
2107
2179
|
if (effectiveDir)
|
|
2108
2180
|
replyText += `\n📂 ${t('cmd.watch.working_dir', { dir: effectiveDir }, loc)}`;
|
|
2181
|
+
else
|
|
2182
|
+
replyText += `\n\n${t(ds ? 'cmd.watch.project_optional_session' : 'cmd.watch.project_optional_lazy', undefined, loc)}`;
|
|
2109
2183
|
if (ds && deps.prewarmDocCommentSession) {
|
|
2110
2184
|
try {
|
|
2111
2185
|
await deps.prewarmDocCommentSession(ds, subscription);
|
|
@@ -2124,6 +2198,95 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
2124
2198
|
}
|
|
2125
2199
|
break;
|
|
2126
2200
|
}
|
|
2201
|
+
case '/vc': {
|
|
2202
|
+
if (!larkAppId) {
|
|
2203
|
+
await sessionReply(rootId, t('cmd.vc.no_session', undefined, loc));
|
|
2204
|
+
break;
|
|
2205
|
+
}
|
|
2206
|
+
const ownerOpenId = getOwnerOpenId(larkAppId);
|
|
2207
|
+
if (!ownerOpenId || message.senderId !== ownerOpenId) {
|
|
2208
|
+
await sessionReply(rootId, t('cmd.vc.owner_only', undefined, loc));
|
|
2209
|
+
break;
|
|
2210
|
+
}
|
|
2211
|
+
const request = parseVcMeetingPrepareCommand(message.content);
|
|
2212
|
+
const dataDir = config.session.dataDir;
|
|
2213
|
+
if (request.kind === 'usage' || request.kind === 'invalid') {
|
|
2214
|
+
const prefix = request.kind === 'invalid'
|
|
2215
|
+
? `${t('cmd.vc.invalid', undefined, loc)}\n\n`
|
|
2216
|
+
: '';
|
|
2217
|
+
await sessionReply(rootId, prefix + t('cmd.vc.usage', undefined, loc));
|
|
2218
|
+
break;
|
|
2219
|
+
}
|
|
2220
|
+
if (request.kind === 'status') {
|
|
2221
|
+
const requestedRecord = request.meetingNo
|
|
2222
|
+
? getVcMeetingPreparation(dataDir, larkAppId, request.meetingNo)
|
|
2223
|
+
: undefined;
|
|
2224
|
+
const records = request.meetingNo
|
|
2225
|
+
? (requestedRecord ? [requestedRecord] : [])
|
|
2226
|
+
: listVcMeetingPreparations(dataDir, larkAppId);
|
|
2227
|
+
if (records.length === 0) {
|
|
2228
|
+
await sessionReply(rootId, t('cmd.vc.none', undefined, loc));
|
|
2229
|
+
break;
|
|
2230
|
+
}
|
|
2231
|
+
const lines = records.map(record => [
|
|
2232
|
+
`• ${record.topic || record.meetingNo}`,
|
|
2233
|
+
` meetingNo: \`${record.meetingNo}\``,
|
|
2234
|
+
` chat: \`${record.prepChatId}\``,
|
|
2235
|
+
` agent: \`${record.agentAppId}\``,
|
|
2236
|
+
` Q&A: ${record.qaMode}`,
|
|
2237
|
+
].join('\n'));
|
|
2238
|
+
await sessionReply(rootId, [t('cmd.vc.status_title', undefined, loc), '', ...lines].join('\n'));
|
|
2239
|
+
break;
|
|
2240
|
+
}
|
|
2241
|
+
if (request.kind === 'off') {
|
|
2242
|
+
let count = 0;
|
|
2243
|
+
if (request.all) {
|
|
2244
|
+
for (const record of listVcMeetingPreparations(dataDir, larkAppId)) {
|
|
2245
|
+
if (removeVcMeetingPreparation(dataDir, larkAppId, record.meetingNo))
|
|
2246
|
+
count += 1;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
else if (request.meetingNo) {
|
|
2250
|
+
count = removeVcMeetingPreparation(dataDir, larkAppId, request.meetingNo) ? 1 : 0;
|
|
2251
|
+
}
|
|
2252
|
+
else if (ds) {
|
|
2253
|
+
count = removeVcMeetingPreparationsByChat(dataDir, larkAppId, ds.chatId);
|
|
2254
|
+
}
|
|
2255
|
+
await sessionReply(rootId, count > 0
|
|
2256
|
+
? t('cmd.vc.stopped', { count }, loc)
|
|
2257
|
+
: t('cmd.vc.none', undefined, loc));
|
|
2258
|
+
break;
|
|
2259
|
+
}
|
|
2260
|
+
if (!ds || ds.chatType !== 'group' || ds.scope !== 'chat') {
|
|
2261
|
+
await sessionReply(rootId, t('cmd.vc.need_group_chat', undefined, loc));
|
|
2262
|
+
break;
|
|
2263
|
+
}
|
|
2264
|
+
const existingInChat = findVcMeetingPreparationByChat(dataDir, larkAppId, ds.chatId);
|
|
2265
|
+
const record = putVcMeetingPreparation(dataDir, {
|
|
2266
|
+
larkAppId,
|
|
2267
|
+
meetingNo: request.meetingNo,
|
|
2268
|
+
...(request.meetingLink ? { meetingLink: request.meetingLink } : {}),
|
|
2269
|
+
prepChatId: ds.chatId,
|
|
2270
|
+
agentAppId: larkAppId,
|
|
2271
|
+
agentSessionId: ds.session.sessionId,
|
|
2272
|
+
ownerOpenId: message.senderId,
|
|
2273
|
+
qaMode: request.qaMode,
|
|
2274
|
+
});
|
|
2275
|
+
const replaced = existingInChat && existingInChat.meetingNo !== record.meetingNo
|
|
2276
|
+
? `\n${t('cmd.vc.replaced', { meetingNo: existingInChat.meetingNo }, loc)}`
|
|
2277
|
+
: '';
|
|
2278
|
+
let preparedText = t('cmd.vc.prepared', {
|
|
2279
|
+
meetingNo: record.meetingNo,
|
|
2280
|
+
qaMode: record.qaMode,
|
|
2281
|
+
}, loc) + replaced;
|
|
2282
|
+
const meetingProjectDir = ds.workingDir ?? ds.session.workingDir;
|
|
2283
|
+
preparedText += meetingProjectDir
|
|
2284
|
+
? `\n\n${t('cmd.vc.project_bound', { dir: meetingProjectDir }, loc)}`
|
|
2285
|
+
: `\n\n${t('cmd.vc.project_optional', undefined, loc)}`;
|
|
2286
|
+
await sessionReply(rootId, preparedText);
|
|
2287
|
+
logger.info(`[${logTag}] /vc prepare meetingNo=${record.meetingNo} chat=${record.prepChatId} agent=${record.agentAppId} qa=${record.qaMode}`);
|
|
2288
|
+
break;
|
|
2289
|
+
}
|
|
2127
2290
|
case '/adopt': {
|
|
2128
2291
|
const adoptArgs = message.content.replace(/^\/adopt\s*/i, '').trim();
|
|
2129
2292
|
if (ds?.adoptedFrom) {
|
|
@@ -2470,6 +2633,11 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
2470
2633
|
* the @-mentioned bots, then migrate every bot's session in this
|
|
2471
2634
|
* thread (including the leader's) into the new chat.
|
|
2472
2635
|
*
|
|
2636
|
+
* p2p (私聊) variant: `/relay --create [群名]` with NO mentions — DMs
|
|
2637
|
+
* have no member roster so @-ing a bot is impossible there. The bot
|
|
2638
|
+
* itself is the sole participant and leader; the new group is user +
|
|
2639
|
+
* this bot, and the DM session migrates over (solo relay, no peers).
|
|
2640
|
+
*
|
|
2473
2641
|
* Two-path command:
|
|
2474
2642
|
* • `--create` (PR2) — implemented below; creates a new chat.
|
|
2475
2643
|
* • no flag (PR3) — picker card listing user's relayable sessions
|
|
@@ -2552,6 +2720,29 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
2552
2720
|
});
|
|
2553
2721
|
const targetScope = targetRouting.scope;
|
|
2554
2722
|
const targetAnchor = targetRouting.anchor;
|
|
2723
|
+
// ── Reply WHERE the user typed /relay ─────────────────────────────
|
|
2724
|
+
// Not through sessionReply: in chat-scope groups (普通群扁平 /
|
|
2725
|
+
// chat-topic / shared) that path either leaks to the chat top level
|
|
2726
|
+
// (the /relay scratch has no turn state to fold back into) or lands
|
|
2727
|
+
// in the CURRENT turn's 话题 (a real chat-scope session) — both away
|
|
2728
|
+
// from the 话题 the user invoked in (申晗 live 反馈). The target
|
|
2729
|
+
// routing already encodes the invocation spot:
|
|
2730
|
+
// thread → reply_in_thread into that 话题 (for 话题群 / DM-thread
|
|
2731
|
+
// top-level this seeds the 话题 on the /relay message —
|
|
2732
|
+
// same place the relayed session will land);
|
|
2733
|
+
// chat → quote-reply the /relay message at the top level.
|
|
2734
|
+
// Fallback to sessionReply if the reply API refuses (e.g. the
|
|
2735
|
+
// command message was withdrawn mid-flight).
|
|
2736
|
+
const replyAtInvocation = async (content, msgType) => {
|
|
2737
|
+
try {
|
|
2738
|
+
await replyMessage(myAppId, targetScope === 'thread' ? targetAnchor : message.messageId, content, msgType ?? 'text',
|
|
2739
|
+
/*replyInThread*/ targetScope === 'thread');
|
|
2740
|
+
}
|
|
2741
|
+
catch (err) {
|
|
2742
|
+
logger.warn(`[${logTag}] /relay reply-at-invocation failed (${err instanceof Error ? err.message : err}); falling back to sessionReply`);
|
|
2743
|
+
await sessionReply(rootId, content, msgType);
|
|
2744
|
+
}
|
|
2745
|
+
};
|
|
2555
2746
|
// ── Existing-session guard (anchor-based) ─────────────────────────
|
|
2556
2747
|
// A real session already sitting AT the target anchor would collide
|
|
2557
2748
|
// on sessionKey(targetAnchor, larkAppId) after transfer — Map.set
|
|
@@ -2566,7 +2757,7 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
2566
2757
|
&& !!c.worker // real running session, not a placeholder
|
|
2567
2758
|
);
|
|
2568
2759
|
if (conflict) {
|
|
2569
|
-
await
|
|
2760
|
+
await replyAtInvocation(t('cmd.relay.target_has_session', { title: conflict.session.title || conflict.session.sessionId.substring(0, 8) }, loc));
|
|
2570
2761
|
break;
|
|
2571
2762
|
}
|
|
2572
2763
|
// Shared candidate-collection logic — used here at initial render
|
|
@@ -2578,7 +2769,7 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
2578
2769
|
const entries = await collectRelayPickerEntries(activeSessions, myAppId, targetAnchor, operatorOpenId);
|
|
2579
2770
|
const { buildRelayPickerCard } = await import('../im/lark/card-builder.js');
|
|
2580
2771
|
const card = buildRelayPickerCard(entries, targetChatId, targetAnchor, operatorOpenId, loc, undefined, targetScope, targetChatType);
|
|
2581
|
-
await
|
|
2772
|
+
await replyAtInvocation(card, 'interactive');
|
|
2582
2773
|
break;
|
|
2583
2774
|
}
|
|
2584
2775
|
const afterFlag = argsLine.replace(/^--create\s*/i, '').trim();
|
|
@@ -2618,30 +2809,38 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
2618
2809
|
await sessionReply(rootId, t('cmd.relay.not_started_yet', undefined, loc));
|
|
2619
2810
|
break;
|
|
2620
2811
|
}
|
|
2812
|
+
// ── p2p (私聊) solo relay: no mention gate, no leader election ──────
|
|
2813
|
+
// 飞书私聊里 @ 不到任何机器人(DM 没有成员列表),mention 门与 leader
|
|
2814
|
+
// 选举在这里没有意义 —— 本 bot 就是唯一参与者兼 leader,新群 = 用户 +
|
|
2815
|
+
// 本 bot,无 peer 协调(peerAppIds 自然为空)。群聊路径语义不变。
|
|
2816
|
+
// chatType 取自 ds(会话创建时从 Lark 事件记录,权威、不漂移)。
|
|
2817
|
+
const sourceIsP2p = ds.chatType === 'p2p';
|
|
2621
2818
|
// ── Mention parsing & leader election (mirror of /group) ───────────
|
|
2622
2819
|
const mentions = message.mentions ?? [];
|
|
2623
2820
|
const knownBotNames = globalKnownBotNames();
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2821
|
+
const botMentions = sourceIsP2p ? [] : mentions.filter(m => m.name && knownBotNames.has(m.name.toLowerCase()));
|
|
2822
|
+
if (!sourceIsP2p) {
|
|
2823
|
+
if (knownBotNames.size === 0 && mentions.some(m => !!m.name)) {
|
|
2824
|
+
logger.warn(`[${logTag}] /relay --create: global bot registry empty; cannot elect a creator`);
|
|
2825
|
+
await sessionReply(rootId, t('cmd.relay.resolve_failed', undefined, loc));
|
|
2826
|
+
break;
|
|
2827
|
+
}
|
|
2828
|
+
if (botMentions.length === 0) {
|
|
2829
|
+
await sessionReply(rootId, t('cmd.relay.no_mentions', undefined, loc));
|
|
2830
|
+
break;
|
|
2831
|
+
}
|
|
2832
|
+
// Am I `mentions[0]`?
|
|
2833
|
+
const firstBot = botMentions[0];
|
|
2834
|
+
const myOpenId = getBotOpenId(creatorAppId);
|
|
2835
|
+
const myName = getBot(creatorAppId).botName?.toLowerCase();
|
|
2836
|
+
const myNameAmbiguous = !!myName
|
|
2837
|
+
&& botMentions.filter(m => m.name?.toLowerCase() === myName).length > 1;
|
|
2838
|
+
const iAmFirstBot = (!!myOpenId && firstBot.openId === myOpenId) ||
|
|
2839
|
+
(!myOpenId && !!myName && !myNameAmbiguous && firstBot.name?.toLowerCase() === myName);
|
|
2840
|
+
if (!iAmFirstBot) {
|
|
2841
|
+
logger.info(`[${logTag}] /relay --create: not the first @-mentioned bot, staying silent`);
|
|
2842
|
+
break;
|
|
2843
|
+
}
|
|
2645
2844
|
}
|
|
2646
2845
|
// Owner-only — only the source session owner may relay this session.
|
|
2647
2846
|
if (ds.session.ownerOpenId && ds.session.ownerOpenId !== senderOpenId) {
|
|
@@ -2649,38 +2848,45 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
2649
2848
|
break;
|
|
2650
2849
|
}
|
|
2651
2850
|
// ── Resolve @-bots to larkAppIds via the source chat's bot roster ──
|
|
2851
|
+
// p2p: 跳过成员表解析(DM 没有 bot roster,listChatBotMembers 会失败),
|
|
2852
|
+
// 参与者就是本 bot 自己;名字兜底走 botDisplayName(nameOf)。
|
|
2652
2853
|
const sourceChatId = ds.chatId;
|
|
2653
|
-
let members = [];
|
|
2654
|
-
try {
|
|
2655
|
-
members = await listChatBotMembers(creatorAppId, sourceChatId);
|
|
2656
|
-
}
|
|
2657
|
-
catch (e) {
|
|
2658
|
-
logger.warn(`[${logTag}] /relay --create: failed to list source chat members: ${e?.message ?? e}`);
|
|
2659
|
-
}
|
|
2660
|
-
const memberByOpenId = new Map(members.map(m => [m.openId, m]));
|
|
2661
2854
|
const appIdToName = new Map();
|
|
2662
|
-
for (const m of members) {
|
|
2663
|
-
if (m.larkAppId && m.displayName)
|
|
2664
|
-
appIdToName.set(m.larkAppId, m.displayName);
|
|
2665
|
-
}
|
|
2666
2855
|
const mentionedBotAppIds = [];
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2856
|
+
if (sourceIsP2p) {
|
|
2857
|
+
mentionedBotAppIds.push(creatorAppId);
|
|
2858
|
+
}
|
|
2859
|
+
else {
|
|
2860
|
+
let members = [];
|
|
2861
|
+
try {
|
|
2862
|
+
members = await listChatBotMembers(creatorAppId, sourceChatId);
|
|
2674
2863
|
}
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2864
|
+
catch (e) {
|
|
2865
|
+
logger.warn(`[${logTag}] /relay --create: failed to list source chat members: ${e?.message ?? e}`);
|
|
2866
|
+
}
|
|
2867
|
+
const memberByOpenId = new Map(members.map(m => [m.openId, m]));
|
|
2868
|
+
for (const m of members) {
|
|
2869
|
+
if (m.larkAppId && m.displayName)
|
|
2870
|
+
appIdToName.set(m.larkAppId, m.displayName);
|
|
2871
|
+
}
|
|
2872
|
+
const seenApp = new Set();
|
|
2873
|
+
let unresolved;
|
|
2874
|
+
for (const bm of botMentions) {
|
|
2875
|
+
const mem = bm.openId ? memberByOpenId.get(bm.openId) : undefined;
|
|
2876
|
+
if (!mem || !mem.larkAppId) {
|
|
2877
|
+
unresolved = bm.name;
|
|
2878
|
+
break;
|
|
2879
|
+
}
|
|
2880
|
+
if (!seenApp.has(mem.larkAppId)) {
|
|
2881
|
+
seenApp.add(mem.larkAppId);
|
|
2882
|
+
mentionedBotAppIds.push(mem.larkAppId);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
if (unresolved) {
|
|
2886
|
+
logger.warn(`[${logTag}] /relay --create: unresolved bot "${unresolved}"`);
|
|
2887
|
+
await sessionReply(rootId, t('cmd.relay.resolve_failed', undefined, loc));
|
|
2888
|
+
break;
|
|
2678
2889
|
}
|
|
2679
|
-
}
|
|
2680
|
-
if (unresolved) {
|
|
2681
|
-
logger.warn(`[${logTag}] /relay --create: unresolved bot "${unresolved}"`);
|
|
2682
|
-
await sessionReply(rootId, t('cmd.relay.resolve_failed', undefined, loc));
|
|
2683
|
-
break;
|
|
2684
2890
|
}
|
|
2685
2891
|
// ── Group name extraction (mirror of /group) ───────────────────────
|
|
2686
2892
|
let rawArgs = afterFlag;
|
|
@@ -2742,10 +2948,14 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
2742
2948
|
const placeholderRootMessageId = newChatId;
|
|
2743
2949
|
// Resolve friendly source-chat label for the M1 body — falls back to
|
|
2744
2950
|
// raw chatId if Lark can't return a name. Mirrors picker-path
|
|
2745
|
-
// (card-handler.ts
|
|
2746
|
-
// entry points.
|
|
2951
|
+
// (card-handler.ts relay_confirm) so the message reads the same in
|
|
2952
|
+
// both UX entry points; p2p source has no chat name (chat.get often
|
|
2953
|
+
// fails/returns empty for DMs) — use the locale-aware 单聊 label
|
|
2954
|
+
// instead of leaking a raw oc_ id into the M1.
|
|
2747
2955
|
const { getChatName } = await import('../im/lark/client.js');
|
|
2748
|
-
const sourceLabel =
|
|
2956
|
+
const sourceLabel = sourceIsP2p
|
|
2957
|
+
? t('card.relay.type_p2p', undefined, loc)
|
|
2958
|
+
: (await getChatName(creatorAppId, sourceChatId).catch(() => null)) ?? sourceChatId;
|
|
2749
2959
|
// ── Step 1: leader transfers its own session (if any) ───────────────
|
|
2750
2960
|
// Empty-leader handling: daemon auto-creates a placeholder ds for any
|
|
2751
2961
|
// DAEMON_COMMAND (worker:null + hasHistory:false). If the user typed
|
|
@@ -3009,6 +3219,7 @@ export async function handleCommand(cmd, rootId, message, deps, larkAppId) {
|
|
|
3009
3219
|
t('help.land', undefined, loc),
|
|
3010
3220
|
t('help.subscribe_doc', undefined, loc),
|
|
3011
3221
|
t('help.watch_comment', undefined, loc),
|
|
3222
|
+
t('help.vc', undefined, loc),
|
|
3012
3223
|
t('help.summary', undefined, loc),
|
|
3013
3224
|
'',
|
|
3014
3225
|
t('help.heading_passthrough', { cliName }, loc),
|