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
|
@@ -29,7 +29,7 @@ import { getSessionWorkingDir, buildNewTopicCliInput, getAvailableBots, persistS
|
|
|
29
29
|
import { publishAttentionPatch, announcePendingRepoSession } from '../../core/session-activity.js';
|
|
30
30
|
import { fallbackTurnId } from '../../core/reply-target.js';
|
|
31
31
|
import { validateWorkingDir } from '../../core/working-dir.js';
|
|
32
|
-
import { sessionKey, sessionAnchorId, frozenDisplayMode } from '../../core/types.js';
|
|
32
|
+
import { sessionKey, sessionAnchorId, frozenDisplayMode, markRepoCardConsumed, isActiveRepoCard, claimCurrentRepoCard } from '../../core/types.js';
|
|
33
33
|
import { buildTerminalUrl } from '../../core/terminal-url.js';
|
|
34
34
|
import { createRepoWorktree, removeRepoWorktree, dirSuffixForBranch, pushWorktreeBranch } from '../../services/git-worktree.js';
|
|
35
35
|
import { withCodexAppContext } from '../../utils/codex-app-context.js';
|
|
@@ -207,6 +207,11 @@ export async function commitRepoSelection(ctx, dirPath, dirLabel,
|
|
|
207
207
|
// The worktree flow already posted a precise "worktree 已创建:path 分支 …"
|
|
208
208
|
// line before funnelling in here — suppress the redundant "已选择/已切换"
|
|
209
209
|
// confirmation so the user sees a single message, not two.
|
|
210
|
+
// pinWorkingDir=false: "直接开始"/skip keeps the default cwd for this launch
|
|
211
|
+
// without persisting it (esp. $HOME) onto session.workingDir — sibling bots
|
|
212
|
+
// must still get their own repo card instead of inheriting HOME.
|
|
213
|
+
// confirmReplyText: optional confirmation to send under the same claim
|
|
214
|
+
// (used by skip_repo so the post-fork reply window cannot race a second click).
|
|
210
215
|
opts) {
|
|
211
216
|
const { ds, rootId, cardMessageId, larkAppId, operatorOpenId, activeSessions, sessionReply } = ctx;
|
|
212
217
|
const locTarget = localeForBot(ds.larkAppId);
|
|
@@ -216,85 +221,170 @@ opts) {
|
|
|
216
221
|
const repoSessionKey = larkAppId ? sessionKey(rootId, larkAppId) : rootId;
|
|
217
222
|
const sessionStillActive = () => activeSessions.get(repoSessionKey) === ds;
|
|
218
223
|
const commitGenSessionId = ds.session.sessionId;
|
|
224
|
+
const pinWorkingDir = opts?.pinWorkingDir !== false;
|
|
219
225
|
if (ds.pendingRepo) {
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
ds.session.riffRepoDirs = opts?.riffRepoDirs;
|
|
226
|
-
sessionStore.updateSession(ds.session);
|
|
227
|
-
const selfBot = getBot(ds.larkAppId);
|
|
228
|
-
const botCfg = selfBot.config;
|
|
229
|
-
const effectiveCliId = sessionCliId(ds);
|
|
230
|
-
// First-time repo selection — now spawn CLI with the original prompt
|
|
231
|
-
ds.pendingRepo = false;
|
|
232
|
-
publishAttentionPatch(ds);
|
|
233
|
-
const pendingPrompt = ds.pendingPrompt ?? '';
|
|
234
|
-
const pendingRawInput = ds.pendingRawInput;
|
|
235
|
-
// Raw-input cold start still wraps any input buffered while the repo card
|
|
236
|
-
// was pending — see the skip_repo branch for the rationale.
|
|
237
|
-
const hasBufferedInput = pendingPrompt.trim().length > 0 ||
|
|
238
|
-
(ds.pendingAttachments?.length ?? 0) > 0 ||
|
|
239
|
-
(ds.pendingFollowUps?.length ?? 0) > 0;
|
|
240
|
-
if (!pendingRawInput || hasBufferedInput)
|
|
241
|
-
ensureSessionWhiteboard(ds);
|
|
242
|
-
const wrappedInput = (!pendingRawInput || hasBufferedInput)
|
|
243
|
-
? buildNewTopicCliInput(pendingPrompt, ds.session.sessionId, effectiveCliId, botCfg.cliPathOverride, ds.pendingAttachments, ds.pendingMentions, await getAvailableBots(ds.larkAppId, ds.chatId), ds.pendingFollowUps, { name: selfBot.botName, openId: selfBot.botOpenId }, locTarget, ds.pendingSender, {
|
|
244
|
-
larkAppId: ds.larkAppId,
|
|
245
|
-
chatId: ds.chatId,
|
|
246
|
-
whiteboardId: ds.session.whiteboardId,
|
|
247
|
-
substituteTrigger: ds.pendingSubstituteTrigger,
|
|
248
|
-
codexAppText: ds.pendingCodexAppText,
|
|
249
|
-
codexAppApplicationContext: ds.pendingCodexAppApplicationContext,
|
|
250
|
-
codexAppMessageContext: ds.pendingCodexAppMessageContext,
|
|
251
|
-
codexAppFollowUps: ds.pendingCodexAppFollowUps,
|
|
252
|
-
codexAppFollowUpContexts: ds.pendingCodexAppFollowUpContexts,
|
|
253
|
-
})
|
|
254
|
-
: { content: '' };
|
|
255
|
-
const prompt = pendingRawInput ? '' : wrappedInput;
|
|
256
|
-
// Last-line defence: prompt prep awaited above — if anything replaced
|
|
257
|
-
// OR closed the session in that window, forking now would clobber it
|
|
258
|
-
// (or resurrect a /close'd session).
|
|
259
|
-
if (!sessionStillActive() || ds.session.sessionId !== commitGenSessionId) {
|
|
260
|
-
logger.warn(`[${tag(ds)}] Session replaced or closed while preparing the pending-CLI prompt (${commitGenSessionId} → ${ds.session.sessionId}, active=${sessionStillActive()}) — aborting this fork`);
|
|
226
|
+
// Card select/skip and auto-worktree converge here; the text /repo path
|
|
227
|
+
// mirrors this transaction through the same in-memory claim. Only one may
|
|
228
|
+
// own the pending -> worker transition, including prompt preparation.
|
|
229
|
+
if (ds.pendingRepoCommitInFlight) {
|
|
230
|
+
logger.info(`[${tag(ds)}] Pending repo commit already in flight — ignoring duplicate selection`);
|
|
261
231
|
return;
|
|
262
232
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
233
|
+
ds.pendingRepoCommitInFlight = true;
|
|
234
|
+
let committed = false;
|
|
235
|
+
try {
|
|
236
|
+
// Claim before mutating the selected directory so two different card
|
|
237
|
+
// choices cannot overwrite each other while one prompt is being built.
|
|
238
|
+
// Skip/bare-start may intentionally leave workingDir unpinned so the
|
|
239
|
+
// launch uses the bot default without persisting $HOME for siblings.
|
|
240
|
+
if (pinWorkingDir) {
|
|
241
|
+
ds.workingDir = dirPath;
|
|
242
|
+
ds.session.workingDir = dirPath;
|
|
243
|
+
}
|
|
244
|
+
ds.session.riffRepoDirs = opts?.riffRepoDirs;
|
|
245
|
+
sessionStore.updateSession(ds.session);
|
|
246
|
+
const selfBot = getBot(ds.larkAppId);
|
|
247
|
+
const botCfg = selfBot.config;
|
|
248
|
+
const effectiveCliId = sessionCliId(ds);
|
|
249
|
+
// Keep pendingRepo=true across this await. New topic messages therefore
|
|
250
|
+
// remain buffered on this same session instead of racing a worker-null
|
|
251
|
+
// safety-net fork. Snapshot every pending field only after it settles.
|
|
252
|
+
const needsPromptContext = !ds.pendingRawInput ||
|
|
253
|
+
(ds.pendingPrompt?.trim().length ?? 0) > 0 ||
|
|
254
|
+
(ds.pendingAttachments?.length ?? 0) > 0 ||
|
|
255
|
+
(ds.pendingFollowUps?.length ?? 0) > 0;
|
|
256
|
+
const availableBots = needsPromptContext
|
|
257
|
+
? await getAvailableBots(ds.larkAppId, ds.chatId)
|
|
258
|
+
: [];
|
|
259
|
+
if (!sessionStillActive() || ds.session.sessionId !== commitGenSessionId ||
|
|
260
|
+
!ds.pendingRepo || (ds.worker && !ds.worker.killed)) {
|
|
261
|
+
logger.warn(`[${tag(ds)}] Session changed while preparing the pending-CLI prompt (${commitGenSessionId} → ${ds.session.sessionId}, active=${sessionStillActive()}, pending=${!!ds.pendingRepo}, worker=${!!ds.worker}) — aborting this fork`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const pendingPrompt = ds.pendingPrompt ?? '';
|
|
265
|
+
const pendingRawInput = ds.pendingRawInput;
|
|
266
|
+
const hasBufferedInput = pendingPrompt.trim().length > 0 ||
|
|
267
|
+
(ds.pendingAttachments?.length ?? 0) > 0 ||
|
|
268
|
+
(ds.pendingFollowUps?.length ?? 0) > 0;
|
|
269
|
+
if (!pendingRawInput || hasBufferedInput)
|
|
270
|
+
ensureSessionWhiteboard(ds);
|
|
271
|
+
const wrappedInput = (!pendingRawInput || hasBufferedInput)
|
|
272
|
+
? buildNewTopicCliInput(pendingPrompt, ds.session.sessionId, effectiveCliId, botCfg.cliPathOverride, ds.pendingAttachments, ds.pendingMentions, availableBots, ds.pendingFollowUps, { name: selfBot.botName, openId: selfBot.botOpenId }, locTarget, ds.pendingSender, {
|
|
273
|
+
larkAppId: ds.larkAppId,
|
|
274
|
+
chatId: ds.chatId,
|
|
275
|
+
whiteboardId: ds.session.whiteboardId,
|
|
276
|
+
substituteTrigger: ds.pendingSubstituteTrigger,
|
|
277
|
+
codexAppText: ds.pendingCodexAppText,
|
|
278
|
+
codexAppApplicationContext: ds.pendingCodexAppApplicationContext,
|
|
279
|
+
codexAppMessageContext: ds.pendingCodexAppMessageContext,
|
|
280
|
+
codexAppFollowUps: ds.pendingCodexAppFollowUps,
|
|
281
|
+
codexAppFollowUpContexts: ds.pendingCodexAppFollowUpContexts,
|
|
282
|
+
})
|
|
283
|
+
: { content: '' };
|
|
284
|
+
const prompt = pendingRawInput ? '' : wrappedInput;
|
|
285
|
+
const pendingTurnId = ds.pendingTurnId;
|
|
286
|
+
const previousPendingFollowUpInput = ds.pendingFollowUpInput;
|
|
287
|
+
if (pendingRawInput && hasBufferedInput) {
|
|
288
|
+
ds.pendingFollowUpInput = {
|
|
289
|
+
userPrompt: ds.pendingCodexAppText !== undefined || ds.pendingCodexAppFollowUps
|
|
290
|
+
? [ds.pendingCodexAppText ?? '', ...(ds.pendingCodexAppFollowUps ?? [])].filter(Boolean).join('\n\n')
|
|
291
|
+
: pendingPrompt || ds.pendingFollowUps?.join('\n\n') || '',
|
|
292
|
+
cliInput: wrappedInput.content,
|
|
293
|
+
...(ds.pendingFollowUpTurnId ? { turnId: ds.pendingFollowUpTurnId } : {}),
|
|
294
|
+
...(effectiveCliId === 'codex-app' && botCfg.codexAppCleanInput === true && wrappedInput.codexAppInput
|
|
295
|
+
? { codexAppInput: wrappedInput.codexAppInput }
|
|
296
|
+
: {}),
|
|
297
|
+
codexAppInputGateFrozen: true,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
// From here through forkWorker there is no await: publish the state
|
|
301
|
+
// transition atomically from the router's point of view.
|
|
302
|
+
ds.pendingRepo = false;
|
|
303
|
+
publishAttentionPatch(ds);
|
|
304
|
+
try {
|
|
305
|
+
if (pendingRawInput) {
|
|
306
|
+
// pendingRawTurnId is applied at the literal PTY write boundary.
|
|
307
|
+
forkWorker(ds, '', false);
|
|
308
|
+
}
|
|
309
|
+
else if (pendingTurnId && hasBufferedInput) {
|
|
310
|
+
forkWorker(ds, prompt, { turnId: pendingTurnId });
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
forkWorker(ds, prompt);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (e) {
|
|
317
|
+
ds.pendingRepo = true;
|
|
318
|
+
ds.pendingFollowUpInput = previousPendingFollowUpInput;
|
|
319
|
+
publishAttentionPatch(ds);
|
|
320
|
+
throw e;
|
|
321
|
+
}
|
|
322
|
+
rememberLastCliInput(ds, pendingRawInput ?? pendingPrompt, pendingRawInput ?? wrappedInput);
|
|
323
|
+
ds.pendingPrompt = undefined;
|
|
324
|
+
ds.pendingCodexAppText = undefined;
|
|
325
|
+
ds.pendingCodexAppApplicationContext = undefined;
|
|
326
|
+
ds.pendingCodexAppMessageContext = undefined;
|
|
327
|
+
ds.pendingAttachments = undefined;
|
|
328
|
+
ds.pendingMentions = undefined;
|
|
329
|
+
ds.pendingSubstituteTrigger = undefined;
|
|
330
|
+
ds.pendingSender = undefined;
|
|
331
|
+
ds.pendingFollowUps = undefined;
|
|
332
|
+
ds.pendingCodexAppFollowUps = undefined;
|
|
333
|
+
ds.pendingCodexAppFollowUpContexts = undefined;
|
|
334
|
+
ds.pendingFollowUpTurnId = undefined;
|
|
335
|
+
ds.pendingTurnId = undefined;
|
|
336
|
+
committed = true;
|
|
337
|
+
// Local one-shot consume BEFORE any network await. Feishu withdraw is
|
|
338
|
+
// best-effort and may lag/fail; correctness for stale card clicks depends
|
|
339
|
+
// on this mark so a second callback cannot mid-session-switch after claim
|
|
340
|
+
// release (and so a sessionReply throw that jumps to finally still leaves
|
|
341
|
+
// the card locally invalid).
|
|
342
|
+
const cardToWithdraw = cardMessageId ?? ds.repoCardMessageId;
|
|
343
|
+
markRepoCardConsumed(ds, cardToWithdraw);
|
|
344
|
+
ds.repoCardMessageId = undefined;
|
|
345
|
+
// Hold the claim through confirmation + best-effort card withdraw so a
|
|
346
|
+
// concurrent in-flight select still sees pendingRepoCommitInFlight.
|
|
347
|
+
try {
|
|
348
|
+
if (!opts?.suppressConfirmReply) {
|
|
349
|
+
// A card click has no turn of its own — anchor the confirmation to the
|
|
350
|
+
// session's current reply-target turn so a shared fold-back topic keeps
|
|
351
|
+
// it in-thread (same leak as the /repo command path).
|
|
352
|
+
await sessionReply(rootId, t('cmd.repo.selected_in_pending', { name: dirLabel }, locTarget), undefined, fallbackTurnId(ds, undefined));
|
|
353
|
+
}
|
|
354
|
+
else if (opts.confirmReplyText) {
|
|
355
|
+
await sessionReply(rootId, opts.confirmReplyText, undefined, fallbackTurnId(ds, undefined));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
logger.warn(`[${tag(ds)}] Confirm reply after pending repo commit failed: ${e instanceof Error ? e.message : e}`);
|
|
360
|
+
}
|
|
361
|
+
if (cardToWithdraw && larkAppId) {
|
|
362
|
+
try {
|
|
363
|
+
await deleteMessage(larkAppId, cardToWithdraw);
|
|
364
|
+
}
|
|
365
|
+
catch { /* card may already be gone */ }
|
|
366
|
+
}
|
|
274
367
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
ds.pendingCodexAppText = undefined;
|
|
278
|
-
ds.pendingCodexAppApplicationContext = undefined;
|
|
279
|
-
ds.pendingCodexAppMessageContext = undefined;
|
|
280
|
-
ds.pendingAttachments = undefined;
|
|
281
|
-
ds.pendingMentions = undefined;
|
|
282
|
-
ds.pendingSubstituteTrigger = undefined;
|
|
283
|
-
ds.pendingSender = undefined;
|
|
284
|
-
ds.pendingFollowUps = undefined;
|
|
285
|
-
ds.pendingCodexAppFollowUps = undefined;
|
|
286
|
-
ds.pendingCodexAppFollowUpContexts = undefined;
|
|
287
|
-
forkWorker(ds, prompt);
|
|
288
|
-
// A card click has no turn of its own — anchor the confirmation to the
|
|
289
|
-
// session's current reply-target turn so a shared fold-back topic keeps
|
|
290
|
-
// it in-thread (same leak as the /repo command path).
|
|
291
|
-
if (!opts?.suppressConfirmReply) {
|
|
292
|
-
await sessionReply(rootId, t('cmd.repo.selected_in_pending', { name: dirLabel }, locTarget), undefined, fallbackTurnId(ds, undefined));
|
|
368
|
+
finally {
|
|
369
|
+
ds.pendingRepoCommitInFlight = false;
|
|
293
370
|
}
|
|
371
|
+
if (!committed)
|
|
372
|
+
return;
|
|
294
373
|
logger.info(`[${tag(ds)}] Repo selected: ${dirPath}, spawning CLI`);
|
|
374
|
+
return;
|
|
295
375
|
}
|
|
296
376
|
else {
|
|
297
377
|
// Mid-session repo switch — close old session, start fresh.
|
|
378
|
+
// Claim the current card BEFORE killWorker / any await. A concurrent click
|
|
379
|
+
// on the same Feishu card must not pass a second kill+fork while the first
|
|
380
|
+
// switch is still awaiting confirm replies. Correctness does not depend on
|
|
381
|
+
// deleteMessage succeeding.
|
|
382
|
+
const claimedCard = claimCurrentRepoCard(ds, cardMessageId);
|
|
383
|
+
if (cardMessageId && !claimedCard) {
|
|
384
|
+
// Stale / already-claimed card (entry check races can still reach here).
|
|
385
|
+
logger.info(`[${tag(ds)}] Ignoring stale mid-session repo card ${cardMessageId}`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
298
388
|
// Safety net (mirrors the `/repo` text-command path): build the same
|
|
299
389
|
// "session closed" card `/close` emits BEFORE displacing the old session
|
|
300
390
|
// (it reads the live session's identity off `ds`). The new session reuses
|
|
@@ -355,14 +445,22 @@ opts) {
|
|
|
355
445
|
ds.lastScreenStatus = undefined;
|
|
356
446
|
forkWorker(ds, '', false);
|
|
357
447
|
if (!opts?.suppressConfirmReply) {
|
|
358
|
-
|
|
448
|
+
try {
|
|
449
|
+
await sessionReply(rootId, t('cmd.repo.switched_to', { name: dirLabel }, locTarget));
|
|
450
|
+
}
|
|
451
|
+
catch (e) {
|
|
452
|
+
logger.warn(`[${tag(ds)}] Confirm reply after mid-session repo switch failed: ${e instanceof Error ? e.message : e}`);
|
|
453
|
+
}
|
|
359
454
|
}
|
|
360
455
|
logger.info(`[${tag(ds)}] Repo switched to ${dirPath}, new session created`);
|
|
456
|
+
// Best-effort withdraw — card already claimed above.
|
|
457
|
+
if (claimedCard && larkAppId) {
|
|
458
|
+
try {
|
|
459
|
+
await deleteMessage(larkAppId, claimedCard);
|
|
460
|
+
}
|
|
461
|
+
catch { /* best-effort */ }
|
|
462
|
+
}
|
|
361
463
|
}
|
|
362
|
-
// Withdraw the repo selection card
|
|
363
|
-
if (cardMessageId && larkAppId)
|
|
364
|
-
deleteMessage(larkAppId, cardMessageId);
|
|
365
|
-
ds.repoCardMessageId = undefined;
|
|
366
464
|
}
|
|
367
465
|
/**
|
|
368
466
|
* 仅默认目录 + auto-worktree 的**异步**提交:`ds` 必须已注册进 activeSessions 且处于
|
|
@@ -1738,72 +1836,37 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
1738
1836
|
}
|
|
1739
1837
|
if (actionType === 'skip_repo' && ds) {
|
|
1740
1838
|
const locDs = localeForBot(ds.larkAppId);
|
|
1839
|
+
// Only the live posted card may act — covers consumed, wrong card, and
|
|
1840
|
+
// post-restart (repoCardMessageId is in-memory and empty after reboot).
|
|
1841
|
+
if (cardMessageId && !isActiveRepoCard(ds, cardMessageId)) {
|
|
1842
|
+
return { toast: { type: 'info', content: t('cmd.repo.card_already_consumed', undefined, locDs) } };
|
|
1843
|
+
}
|
|
1741
1844
|
if (ds.pendingRepo) {
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
const effectiveCliId = sessionCliId(ds);
|
|
1745
|
-
// Skip repo selection — spawn CLI with default working dir
|
|
1746
|
-
ds.pendingRepo = false;
|
|
1747
|
-
publishAttentionPatch(ds);
|
|
1748
|
-
const pendingPrompt = ds.pendingPrompt ?? '';
|
|
1749
|
-
const pendingRawInput = ds.pendingRawInput;
|
|
1750
|
-
// Raw-input cold start still wraps any input buffered while the repo
|
|
1751
|
-
// card was pending (follow-ups / attachments) — delivered right after
|
|
1752
|
-
// the raw input on prompt_ready instead of being dropped.
|
|
1753
|
-
const hasBufferedInput = pendingPrompt.trim().length > 0 ||
|
|
1754
|
-
(ds.pendingAttachments?.length ?? 0) > 0 ||
|
|
1755
|
-
(ds.pendingFollowUps?.length ?? 0) > 0;
|
|
1756
|
-
if (!pendingRawInput || hasBufferedInput)
|
|
1757
|
-
ensureSessionWhiteboard(ds);
|
|
1758
|
-
const wrappedInput = (!pendingRawInput || hasBufferedInput)
|
|
1759
|
-
? buildNewTopicCliInput(pendingPrompt, ds.session.sessionId, effectiveCliId, botCfg.cliPathOverride, ds.pendingAttachments, ds.pendingMentions, await getAvailableBots(ds.larkAppId, ds.chatId), ds.pendingFollowUps, { name: selfBot.botName, openId: selfBot.botOpenId }, locDs, ds.pendingSender, {
|
|
1760
|
-
larkAppId: ds.larkAppId,
|
|
1761
|
-
chatId: ds.chatId,
|
|
1762
|
-
whiteboardId: ds.session.whiteboardId,
|
|
1763
|
-
substituteTrigger: ds.pendingSubstituteTrigger,
|
|
1764
|
-
codexAppText: ds.pendingCodexAppText,
|
|
1765
|
-
codexAppApplicationContext: ds.pendingCodexAppApplicationContext,
|
|
1766
|
-
codexAppMessageContext: ds.pendingCodexAppMessageContext,
|
|
1767
|
-
codexAppFollowUps: ds.pendingCodexAppFollowUps,
|
|
1768
|
-
codexAppFollowUpContexts: ds.pendingCodexAppFollowUpContexts,
|
|
1769
|
-
})
|
|
1770
|
-
: { content: '' };
|
|
1771
|
-
const prompt = pendingRawInput ? '' : wrappedInput;
|
|
1772
|
-
if (pendingRawInput && hasBufferedInput) {
|
|
1773
|
-
ds.pendingFollowUpInput = {
|
|
1774
|
-
userPrompt: ds.pendingCodexAppText !== undefined || ds.pendingCodexAppFollowUps
|
|
1775
|
-
? [ds.pendingCodexAppText ?? '', ...(ds.pendingCodexAppFollowUps ?? [])].filter(Boolean).join('\n\n')
|
|
1776
|
-
: pendingPrompt || ds.pendingFollowUps?.join('\n\n') || '',
|
|
1777
|
-
cliInput: wrappedInput.content,
|
|
1778
|
-
...(effectiveCliId === 'codex-app' && botCfg.codexAppCleanInput === true && wrappedInput.codexAppInput
|
|
1779
|
-
? { codexAppInput: wrappedInput.codexAppInput }
|
|
1780
|
-
: {}),
|
|
1781
|
-
codexAppInputGateFrozen: true,
|
|
1782
|
-
};
|
|
1845
|
+
if (ds.worktreeCreating || ds.pendingRepoCommitInFlight) {
|
|
1846
|
+
return { toast: { type: 'info', content: t('cmd.repo.worktree_in_progress', undefined, locDs) } };
|
|
1783
1847
|
}
|
|
1784
|
-
rememberLastCliInput(ds, pendingRawInput ?? pendingPrompt, pendingRawInput ?? wrappedInput);
|
|
1785
|
-
ds.pendingPrompt = undefined;
|
|
1786
|
-
ds.pendingCodexAppText = undefined;
|
|
1787
|
-
ds.pendingCodexAppApplicationContext = undefined;
|
|
1788
|
-
ds.pendingCodexAppMessageContext = undefined;
|
|
1789
|
-
ds.pendingAttachments = undefined;
|
|
1790
|
-
ds.pendingMentions = undefined;
|
|
1791
|
-
ds.pendingSubstituteTrigger = undefined;
|
|
1792
|
-
ds.pendingSender = undefined;
|
|
1793
|
-
ds.pendingFollowUps = undefined;
|
|
1794
|
-
ds.pendingCodexAppFollowUps = undefined;
|
|
1795
|
-
ds.pendingCodexAppFollowUpContexts = undefined;
|
|
1796
|
-
forkWorker(ds, prompt);
|
|
1797
1848
|
const cwd = getSessionWorkingDir(ds);
|
|
1798
|
-
|
|
1799
|
-
|
|
1849
|
+
// Reuse the same claimed pending->worker transition as a normal repo
|
|
1850
|
+
// selection. This keeps buffering active across prompt preparation and
|
|
1851
|
+
// makes skip/select races single-winner. Do NOT pin the resolved default
|
|
1852
|
+
// cwd (often $HOME) onto session.workingDir — that would let sibling
|
|
1853
|
+
// bots inherit HOME instead of getting their own repo card. Confirmation
|
|
1854
|
+
// + card withdraw run under the claim inside commitRepoSelection.
|
|
1855
|
+
await commitRepoSelection({ ds, rootId, cardMessageId, larkAppId, operatorOpenId, activeSessions, sessionReply }, cwd, pathBasename(cwd) || cwd, {
|
|
1856
|
+
suppressConfirmReply: true,
|
|
1857
|
+
confirmReplyText: t('cmd.skip.opened', { cwd }, locDs),
|
|
1858
|
+
pinWorkingDir: false,
|
|
1859
|
+
});
|
|
1860
|
+
if (!ds.pendingRepo) {
|
|
1861
|
+
logger.info(`[${tag(ds)}] Skip repo, spawning CLI in ${cwd}`);
|
|
1862
|
+
}
|
|
1800
1863
|
}
|
|
1801
1864
|
else {
|
|
1802
1865
|
await sessionReply(rootId, t('card.action.continue_using_current_repo', { cwd: getSessionWorkingDir(ds) }, locDs));
|
|
1866
|
+
if (cardMessageId && larkAppId)
|
|
1867
|
+
deleteMessage(larkAppId, cardMessageId);
|
|
1868
|
+
ds.repoCardMessageId = undefined;
|
|
1803
1869
|
}
|
|
1804
|
-
if (cardMessageId && larkAppId)
|
|
1805
|
-
deleteMessage(larkAppId, cardMessageId);
|
|
1806
|
-
ds.repoCardMessageId = undefined;
|
|
1807
1870
|
}
|
|
1808
1871
|
// Manual working-directory entry from the repo card form. The project scan
|
|
1809
1872
|
// may not surface every useful directory; this mirrors `/repo <path>` from
|
|
@@ -1812,6 +1875,9 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
1812
1875
|
// needs a scanned git repo root, not an arbitrary path.
|
|
1813
1876
|
if (actionType === 'repo_manual_submit' && ds) {
|
|
1814
1877
|
const locDs = localeForBot(ds.larkAppId);
|
|
1878
|
+
if (cardMessageId && !isActiveRepoCard(ds, cardMessageId)) {
|
|
1879
|
+
return { toast: { type: 'info', content: t('cmd.repo.card_already_consumed', undefined, locDs) } };
|
|
1880
|
+
}
|
|
1815
1881
|
const rawPath = String(action?.form_value?.repo_manual_path ?? '').trim();
|
|
1816
1882
|
if (!rawPath) {
|
|
1817
1883
|
return { toast: { type: 'error', content: t('card.repo.manual_empty', undefined, locDs) } };
|
|
@@ -1822,7 +1888,7 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
1822
1888
|
}
|
|
1823
1889
|
// A worktree creation in flight holds the commit lock — a manual switch
|
|
1824
1890
|
// interleaving there would double-fork (same guard as the plain switch).
|
|
1825
|
-
if (ds.worktreeCreating) {
|
|
1891
|
+
if (ds.worktreeCreating || ds.pendingRepoCommitInFlight) {
|
|
1826
1892
|
return { toast: { type: 'info', content: t('cmd.repo.worktree_in_progress', undefined, locDs) } };
|
|
1827
1893
|
}
|
|
1828
1894
|
const selectedPath = validation.resolvedPath;
|
|
@@ -1834,6 +1900,11 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
1834
1900
|
// re-send a fresh repo card in the new mode — a form can't ride an
|
|
1835
1901
|
// in-place patch, so the old card is withdrawn and a new one posted.
|
|
1836
1902
|
const locDs = localeForBot(ds.larkAppId);
|
|
1903
|
+
// Same active-card gate as skip/manual/worktree: a stale card must not
|
|
1904
|
+
// flip bot config or replace the live repo card after claim/restart.
|
|
1905
|
+
if (cardMessageId && !isActiveRepoCard(ds, cardMessageId)) {
|
|
1906
|
+
return { toast: { type: 'info', content: t('cmd.repo.card_already_consumed', undefined, locDs) } };
|
|
1907
|
+
}
|
|
1837
1908
|
const spec = findConfigField('worktreeMultiPicker');
|
|
1838
1909
|
if (!spec)
|
|
1839
1910
|
return;
|
|
@@ -1856,11 +1927,14 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
1856
1927
|
}
|
|
1857
1928
|
if (actionType === 'repo_worktree_submit' && ds) {
|
|
1858
1929
|
const locDs = localeForBot(ds.larkAppId);
|
|
1930
|
+
if (cardMessageId && !isActiveRepoCard(ds, cardMessageId)) {
|
|
1931
|
+
return { toast: { type: 'info', content: t('cmd.repo.card_already_consumed', undefined, locDs) } };
|
|
1932
|
+
}
|
|
1859
1933
|
const selectedPaths = stringListFromLarkMultiSelect(action?.form_value?.repo_worktree_paths);
|
|
1860
1934
|
if (selectedPaths.length === 0) {
|
|
1861
1935
|
return { toast: { type: 'error', content: t('card.repo.worktree_empty', undefined, locDs) } };
|
|
1862
1936
|
}
|
|
1863
|
-
if (ds.worktreeCreating) {
|
|
1937
|
+
if (ds.worktreeCreating || ds.pendingRepoCommitInFlight) {
|
|
1864
1938
|
return { toast: { type: 'info', content: t('cmd.repo.worktree_in_progress', undefined, locDs) } };
|
|
1865
1939
|
}
|
|
1866
1940
|
const branch = String(action?.form_value?.repo_worktree_branch ?? '').trim() || undefined;
|
|
@@ -2077,6 +2151,12 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
2077
2151
|
logger.warn(`Card action: no active session found for root ${rootId}`);
|
|
2078
2152
|
return;
|
|
2079
2153
|
}
|
|
2154
|
+
// Only the live posted card may drive selection. Covers: already-consumed,
|
|
2155
|
+
// wrong/old card, and post-restart (repoCardMessageId is in-memory). Must
|
|
2156
|
+
// reject before mid-session switch can killWorker.
|
|
2157
|
+
if (cardMessageId && !isActiveRepoCard(targetDs, cardMessageId)) {
|
|
2158
|
+
return { toast: { type: 'info', content: t('cmd.repo.card_already_consumed', undefined, localeForBot(targetDs.larkAppId)) } };
|
|
2159
|
+
}
|
|
2080
2160
|
// 权限边界:pendingRepo(首次选 repo 才能 spawn)放行「会话发起人 或 canOperate」,
|
|
2081
2161
|
// 让本群授权用户能完成自己的首次使用;非 pending 的 mid-session 切换是管理动作,要 canOperate。
|
|
2082
2162
|
const isSessionOwnerOp = !!operatorOpenId && operatorOpenId === targetDs.session.ownerOpenId;
|
|
@@ -2118,7 +2198,7 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
2118
2198
|
// ack the card action immediately with a toast and finish asynchronously.
|
|
2119
2199
|
// On failure the card (and pendingRepo state) stays put so the user can
|
|
2120
2200
|
// pick again or fall back to a plain switch.
|
|
2121
|
-
if (targetDs.worktreeCreating) {
|
|
2201
|
+
if (targetDs.worktreeCreating || targetDs.pendingRepoCommitInFlight) {
|
|
2122
2202
|
// The async path escapes the card-action in-flight dedup — gate repeats
|
|
2123
2203
|
// here, or two creations would race and the loser's commitSelection
|
|
2124
2204
|
// would yank the session the winner just spawned.
|
|
@@ -2245,7 +2325,7 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
2245
2325
|
// worktree commit awaits (Lark replies, prompt prep) after its generation
|
|
2246
2326
|
// checks; a plain selection interleaving there would double-fork. One lock
|
|
2247
2327
|
// gates both kinds until the commit settles.
|
|
2248
|
-
if (targetDs.worktreeCreating) {
|
|
2328
|
+
if (targetDs.worktreeCreating || targetDs.pendingRepoCommitInFlight) {
|
|
2249
2329
|
return { toast: { type: 'info', content: t('cmd.repo.worktree_in_progress', undefined, locTarget) } };
|
|
2250
2330
|
}
|
|
2251
2331
|
await commitRepoSelection(commitCtx, selectedPath, displayName);
|