botmux 2.76.1 → 2.78.0
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/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +2 -1
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +40 -38
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/worker-pool.d.ts +41 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +94 -1
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +12 -5
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +10 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/settings.d.ts.map +1 -1
- package/dist/dashboard/web/settings.js +19 -0
- package/dist/dashboard/web/settings.js.map +1 -1
- package/dist/dashboard-web/app.js +111 -100
- package/dist/dashboard.js +11 -2
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +11 -0
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +13 -0
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +4 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +4 -1
- 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 +91 -11
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +156 -106
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +19 -11
- package/dist/im/lark/client.js.map +1 -1
- package/dist/services/project-scanner.d.ts +5 -2
- package/dist/services/project-scanner.d.ts.map +1 -1
- package/dist/services/project-scanner.js +9 -6
- package/dist/services/project-scanner.js.map +1 -1
- package/dist/services/resumable-session-discovery.d.ts.map +1 -1
- package/dist/services/resumable-session-discovery.js +8 -4
- package/dist/services/resumable-session-discovery.js.map +1 -1
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@ import { forkWorker, killWorker, scheduleCardPatch, parkStreamCard, clearUsageLi
|
|
|
22
22
|
import { getSessionWorkingDir, buildNewTopicPrompt, getAvailableBots, persistStreamCardState, resumeSession, rememberLastCliInput } from '../../core/session-manager.js';
|
|
23
23
|
import { publishAttentionPatch } from '../../core/session-activity.js';
|
|
24
24
|
import { fallbackTurnId } from '../../core/reply-target.js';
|
|
25
|
+
import { validateWorkingDir } from '../../core/working-dir.js';
|
|
25
26
|
import { sessionKey, sessionAnchorId, frozenDisplayMode } from '../../core/types.js';
|
|
26
27
|
import { buildTerminalUrl } from '../../core/terminal-url.js';
|
|
27
28
|
import { createRepoWorktree } from '../../services/git-worktree.js';
|
|
@@ -90,6 +91,125 @@ function validateCardCliBinding(ds, value) {
|
|
|
90
91
|
`action=${value?.action ?? '?'} expected=${expected} actual=${actual}`);
|
|
91
92
|
return false;
|
|
92
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Commit a resolved working directory onto a repo-select session: pin it, then
|
|
96
|
+
* either fork the pending CLI (first selection) or close + recreate the session
|
|
97
|
+
* (mid-session switch). Shared by the dropdown flow, the worktree flow (which
|
|
98
|
+
* funnels back in with the freshly created worktree path) and the manual
|
|
99
|
+
* directory-entry form. Extracted to module scope so the form-submit branch can
|
|
100
|
+
* reuse the exact same spawn/switch path instead of duplicating it.
|
|
101
|
+
*/
|
|
102
|
+
async function commitRepoSelection(ctx, dirPath, dirLabel,
|
|
103
|
+
// The worktree flow already posted a precise "worktree 已创建:path 分支 …"
|
|
104
|
+
// line before funnelling in here — suppress the redundant "已选择/已切换"
|
|
105
|
+
// confirmation so the user sees a single message, not two.
|
|
106
|
+
opts) {
|
|
107
|
+
const { ds, rootId, cardMessageId, larkAppId, activeSessions, sessionReply } = ctx;
|
|
108
|
+
const locTarget = localeForBot(ds.larkAppId);
|
|
109
|
+
// `/close` deletes the active-map entry without touching sessionId or
|
|
110
|
+
// pendingRepo — identity against the map is the only tell that the session
|
|
111
|
+
// this flow captured is gone. Checked alongside the generation snapshots.
|
|
112
|
+
const repoSessionKey = larkAppId ? sessionKey(rootId, larkAppId) : rootId;
|
|
113
|
+
const sessionStillActive = () => activeSessions.get(repoSessionKey) === ds;
|
|
114
|
+
const commitGenSessionId = ds.session.sessionId;
|
|
115
|
+
ds.workingDir = dirPath;
|
|
116
|
+
ds.session.workingDir = dirPath;
|
|
117
|
+
sessionStore.updateSession(ds.session);
|
|
118
|
+
if (ds.pendingRepo) {
|
|
119
|
+
const selfBot = getBot(ds.larkAppId);
|
|
120
|
+
const botCfg = selfBot.config;
|
|
121
|
+
const effectiveCliId = sessionCliId(ds);
|
|
122
|
+
// First-time repo selection — now spawn CLI with the original prompt
|
|
123
|
+
ds.pendingRepo = false;
|
|
124
|
+
publishAttentionPatch(ds);
|
|
125
|
+
const pendingPrompt = ds.pendingPrompt ?? '';
|
|
126
|
+
const pendingRawInput = ds.pendingRawInput;
|
|
127
|
+
// Raw-input cold start still wraps any input buffered while the repo card
|
|
128
|
+
// was pending — see the skip_repo branch for the rationale.
|
|
129
|
+
const hasBufferedInput = pendingPrompt.trim().length > 0 ||
|
|
130
|
+
(ds.pendingAttachments?.length ?? 0) > 0 ||
|
|
131
|
+
(ds.pendingFollowUps?.length ?? 0) > 0;
|
|
132
|
+
const wrappedPrompt = (!pendingRawInput || hasBufferedInput)
|
|
133
|
+
? buildNewTopicPrompt(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, { larkAppId: ds.larkAppId, chatId: ds.chatId })
|
|
134
|
+
: '';
|
|
135
|
+
const prompt = pendingRawInput ? '' : wrappedPrompt;
|
|
136
|
+
// Last-line defence: prompt prep awaited above — if anything replaced
|
|
137
|
+
// OR closed the session in that window, forking now would clobber it
|
|
138
|
+
// (or resurrect a /close'd session).
|
|
139
|
+
if (!sessionStillActive() || ds.session.sessionId !== commitGenSessionId) {
|
|
140
|
+
logger.warn(`[${tag(ds)}] Session replaced or closed while preparing the pending-CLI prompt (${commitGenSessionId} → ${ds.session.sessionId}, active=${sessionStillActive()}) — aborting this fork`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (pendingRawInput && hasBufferedInput) {
|
|
144
|
+
ds.pendingFollowUpInput = {
|
|
145
|
+
userPrompt: pendingPrompt || (ds.pendingFollowUps?.join('\n\n') ?? ''),
|
|
146
|
+
cliInput: wrappedPrompt,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
rememberLastCliInput(ds, pendingRawInput ?? pendingPrompt, pendingRawInput ?? prompt);
|
|
150
|
+
ds.pendingPrompt = undefined;
|
|
151
|
+
ds.pendingAttachments = undefined;
|
|
152
|
+
ds.pendingMentions = undefined;
|
|
153
|
+
ds.pendingSender = undefined;
|
|
154
|
+
ds.pendingFollowUps = undefined;
|
|
155
|
+
forkWorker(ds, prompt);
|
|
156
|
+
// A card click has no turn of its own — anchor the confirmation to the
|
|
157
|
+
// session's current reply-target turn so a shared fold-back topic keeps
|
|
158
|
+
// it in-thread (same leak as the /repo command path).
|
|
159
|
+
if (!opts?.suppressConfirmReply) {
|
|
160
|
+
await sessionReply(rootId, t('cmd.repo.selected_in_pending', { name: dirLabel }, locTarget), undefined, fallbackTurnId(ds, undefined));
|
|
161
|
+
}
|
|
162
|
+
logger.info(`[${tag(ds)}] Repo selected: ${dirPath}, spawning CLI`);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Mid-session repo switch — close old session, start fresh.
|
|
166
|
+
killWorker(ds);
|
|
167
|
+
// Park the current card in `frozenCards` so the next POST under the new
|
|
168
|
+
// session sweeps it via recall. closeSession() wipes the on-disk
|
|
169
|
+
// frozen-cards file under the OLD sessionId, but the in-memory Map
|
|
170
|
+
// travels with `ds` into the new session and still carries the
|
|
171
|
+
// old messageId for deletion. If fork or POST fails, the parked card
|
|
172
|
+
// stays in the thread instead of vanishing prematurely.
|
|
173
|
+
parkStreamCard(ds);
|
|
174
|
+
sessionStore.closeSession(ds.session.sessionId);
|
|
175
|
+
const session = sessionStore.createSession(ds.chatId, rootId, dirLabel, ds.chatType);
|
|
176
|
+
ds.session = session;
|
|
177
|
+
ds.lastUserPrompt = undefined;
|
|
178
|
+
ds.lastCliInput = undefined;
|
|
179
|
+
// Pin workingDir + larkAppId onto the new session before forkWorker.
|
|
180
|
+
// Without this, a daemon restart restores the session with an empty
|
|
181
|
+
// workingDir and the worker spawns in the bot's default cwd, so
|
|
182
|
+
// `claude --resume` looks in the wrong .claude/projects/<hash>/ dir and
|
|
183
|
+
// exits code 0 immediately, crash-looping until the rate-limiter trips.
|
|
184
|
+
ds.session.workingDir = dirPath;
|
|
185
|
+
ds.session.larkAppId = ds.larkAppId;
|
|
186
|
+
sessionStore.updateSession(ds.session);
|
|
187
|
+
ds.hasHistory = false;
|
|
188
|
+
// Re-persist the parked card under the NEW sessionId so a daemon crash
|
|
189
|
+
// before the next POST doesn't strand it. closeSession() above wiped
|
|
190
|
+
// the on-disk file under the OLD sessionId; without this re-save, the
|
|
191
|
+
// in-memory Map only survives in process memory.
|
|
192
|
+
if (ds.frozenCards && ds.frozenCards.size > 0) {
|
|
193
|
+
saveFrozenCards(ds.session.sessionId, ds.frozenCards);
|
|
194
|
+
}
|
|
195
|
+
// Drop the old turn's streaming-card reference so worker_ready POSTs a
|
|
196
|
+
// fresh card for the new session instead of PATCHing the previous one.
|
|
197
|
+
ds.streamCardId = undefined;
|
|
198
|
+
ds.streamCardNonce = undefined;
|
|
199
|
+
ds.streamCardPending = undefined;
|
|
200
|
+
ds.lastScreenContent = undefined;
|
|
201
|
+
ds.lastScreenStatus = undefined;
|
|
202
|
+
forkWorker(ds, '', false);
|
|
203
|
+
if (!opts?.suppressConfirmReply) {
|
|
204
|
+
await sessionReply(rootId, t('cmd.repo.switched_to', { name: dirLabel }, locTarget));
|
|
205
|
+
}
|
|
206
|
+
logger.info(`[${tag(ds)}] Repo switched to ${dirPath}, new session created`);
|
|
207
|
+
}
|
|
208
|
+
// Withdraw the repo selection card
|
|
209
|
+
if (cardMessageId && larkAppId)
|
|
210
|
+
deleteMessage(larkAppId, cardMessageId);
|
|
211
|
+
ds.repoCardMessageId = undefined;
|
|
212
|
+
}
|
|
93
213
|
// ─── Main handler ─────────────────────────────────────────────────────────
|
|
94
214
|
export async function handleCardAction(data, deps, larkAppId) {
|
|
95
215
|
const { activeSessions, lastRepoScan } = deps;
|
|
@@ -602,7 +722,7 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
602
722
|
}
|
|
603
723
|
return { toast: { type: 'success', content: t('card.relay.toast_success', undefined, loc) } };
|
|
604
724
|
}
|
|
605
|
-
const isSensitive = value?.action && ['restart', 'close', 'resume', 'skip_repo', 'retry_last_task', 'get_write_link', 'toggle_stream', 'toggle_display', 'export_text', 'term_action', 'refresh_screenshot', 'takeover', 'disconnect', 'tui_keys', 'tui_text_input', 'wf_approve', 'wf_reject', 'wf_cancel'].includes(value.action);
|
|
725
|
+
const isSensitive = value?.action && ['restart', 'close', 'resume', 'skip_repo', 'repo_manual_submit', 'retry_last_task', 'get_write_link', 'toggle_stream', 'toggle_display', 'export_text', 'term_action', 'refresh_screenshot', 'takeover', 'disconnect', 'tui_keys', 'tui_text_input', 'wf_approve', 'wf_reject', 'wf_cancel'].includes(value.action);
|
|
606
726
|
if (isSensitive) {
|
|
607
727
|
const rootId = value?.root_id;
|
|
608
728
|
// activeSessions is keyed by sessionKey(anchor, larkAppId) — `${anchor}::${larkAppId}`
|
|
@@ -623,9 +743,9 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
623
743
|
: undefined;
|
|
624
744
|
const effectiveAppId = larkAppId ?? ds?.larkAppId ?? closedForCtx?.larkAppId;
|
|
625
745
|
const chatId = ds?.chatId ?? closedForCtx?.chatId;
|
|
626
|
-
// pendingRepo 阶段,会话发起人(含 chat-granted 用户)可以 skip_repo
|
|
627
|
-
//
|
|
628
|
-
const pendingRepoOwnerException = value.action === 'skip_repo' && !!ds?.pendingRepo &&
|
|
746
|
+
// pendingRepo 阶段,会话发起人(含 chat-granted 用户)可以 skip_repo / 手动填目录
|
|
747
|
+
// 起会话——与 repo 下拉选择同款例外,否则被授权人连自己的首次会话都启动不了。
|
|
748
|
+
const pendingRepoOwnerException = (value.action === 'skip_repo' || value.action === 'repo_manual_submit') && !!ds?.pendingRepo &&
|
|
629
749
|
!!operatorOpenId && operatorOpenId === ds.session.ownerOpenId;
|
|
630
750
|
if (effectiveAppId) {
|
|
631
751
|
if (!pendingRepoOwnerException && !canOperate(effectiveAppId, chatId, operatorOpenId)) {
|
|
@@ -1201,6 +1321,30 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
1201
1321
|
deleteMessage(larkAppId, cardMessageId);
|
|
1202
1322
|
ds.repoCardMessageId = undefined;
|
|
1203
1323
|
}
|
|
1324
|
+
// Manual working-directory entry from the repo card form. The project scan
|
|
1325
|
+
// may not surface every useful directory; this mirrors `/repo <path>` from
|
|
1326
|
+
// the card. Permission is gated at the top (isSensitive + pendingRepoOwner
|
|
1327
|
+
// exception), same as skip_repo. Always a plain commit — worktree creation
|
|
1328
|
+
// needs a scanned git repo root, not an arbitrary path.
|
|
1329
|
+
if (actionType === 'repo_manual_submit' && ds) {
|
|
1330
|
+
const locDs = localeForBot(ds.larkAppId);
|
|
1331
|
+
const rawPath = String(action?.form_value?.repo_manual_path ?? '').trim();
|
|
1332
|
+
if (!rawPath) {
|
|
1333
|
+
return { toast: { type: 'error', content: t('card.repo.manual_empty', undefined, locDs) } };
|
|
1334
|
+
}
|
|
1335
|
+
const validation = validateWorkingDir(rawPath, locDs);
|
|
1336
|
+
if (!validation.ok) {
|
|
1337
|
+
return { toast: { type: 'error', content: validation.error } };
|
|
1338
|
+
}
|
|
1339
|
+
// A worktree creation in flight holds the commit lock — a manual switch
|
|
1340
|
+
// interleaving there would double-fork (same guard as the plain switch).
|
|
1341
|
+
if (ds.worktreeCreating) {
|
|
1342
|
+
return { toast: { type: 'info', content: t('cmd.repo.worktree_in_progress', undefined, locDs) } };
|
|
1343
|
+
}
|
|
1344
|
+
const selectedPath = validation.resolvedPath;
|
|
1345
|
+
const displayName = pathBasename(selectedPath) || selectedPath;
|
|
1346
|
+
await commitRepoSelection({ ds, rootId, cardMessageId, larkAppId, activeSessions, sessionReply }, selectedPath, displayName);
|
|
1347
|
+
}
|
|
1204
1348
|
return;
|
|
1205
1349
|
}
|
|
1206
1350
|
// Handle dropdown selections (option-based)
|
|
@@ -1398,106 +1542,10 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
1398
1542
|
// this flow captured is gone. Checked alongside the generation snapshots.
|
|
1399
1543
|
const repoSessionKey = sessionKey(rootId, larkAppId);
|
|
1400
1544
|
const sessionStillActive = () => activeSessions.get(repoSessionKey) === targetDs;
|
|
1401
|
-
// Shared commit
|
|
1402
|
-
//
|
|
1403
|
-
//
|
|
1404
|
-
|
|
1405
|
-
const commitSelection = async (dirPath, dirLabel) => {
|
|
1406
|
-
const commitGenSessionId = targetDs.session.sessionId;
|
|
1407
|
-
targetDs.workingDir = dirPath;
|
|
1408
|
-
targetDs.session.workingDir = dirPath;
|
|
1409
|
-
sessionStore.updateSession(targetDs.session);
|
|
1410
|
-
if (targetDs.pendingRepo) {
|
|
1411
|
-
const selfBot = getBot(targetDs.larkAppId);
|
|
1412
|
-
const botCfg = selfBot.config;
|
|
1413
|
-
const effectiveCliId = sessionCliId(targetDs);
|
|
1414
|
-
// First-time repo selection — now spawn CLI with the original prompt
|
|
1415
|
-
targetDs.pendingRepo = false;
|
|
1416
|
-
publishAttentionPatch(targetDs);
|
|
1417
|
-
const pendingPrompt = targetDs.pendingPrompt ?? '';
|
|
1418
|
-
const pendingRawInput = targetDs.pendingRawInput;
|
|
1419
|
-
// Raw-input cold start still wraps any input buffered while the repo card
|
|
1420
|
-
// was pending — see the skip_repo branch above for the rationale.
|
|
1421
|
-
const hasBufferedInput = pendingPrompt.trim().length > 0 ||
|
|
1422
|
-
(targetDs.pendingAttachments?.length ?? 0) > 0 ||
|
|
1423
|
-
(targetDs.pendingFollowUps?.length ?? 0) > 0;
|
|
1424
|
-
const wrappedPrompt = (!pendingRawInput || hasBufferedInput)
|
|
1425
|
-
? buildNewTopicPrompt(pendingPrompt, targetDs.session.sessionId, effectiveCliId, botCfg.cliPathOverride, targetDs.pendingAttachments, targetDs.pendingMentions, await getAvailableBots(targetDs.larkAppId, targetDs.chatId), targetDs.pendingFollowUps, { name: selfBot.botName, openId: selfBot.botOpenId }, locTarget, targetDs.pendingSender, { larkAppId: targetDs.larkAppId, chatId: targetDs.chatId })
|
|
1426
|
-
: '';
|
|
1427
|
-
const prompt = pendingRawInput ? '' : wrappedPrompt;
|
|
1428
|
-
// Last-line defence: prompt prep awaited above — if anything replaced
|
|
1429
|
-
// OR closed the session in that window, forking now would clobber it
|
|
1430
|
-
// (or resurrect a /close'd session).
|
|
1431
|
-
if (!sessionStillActive() || targetDs.session.sessionId !== commitGenSessionId) {
|
|
1432
|
-
logger.warn(`[${tag(targetDs)}] Session replaced or closed while preparing the pending-CLI prompt (${commitGenSessionId} → ${targetDs.session.sessionId}, active=${sessionStillActive()}) — aborting this fork`);
|
|
1433
|
-
return;
|
|
1434
|
-
}
|
|
1435
|
-
if (pendingRawInput && hasBufferedInput) {
|
|
1436
|
-
targetDs.pendingFollowUpInput = {
|
|
1437
|
-
userPrompt: pendingPrompt || (targetDs.pendingFollowUps?.join('\n\n') ?? ''),
|
|
1438
|
-
cliInput: wrappedPrompt,
|
|
1439
|
-
};
|
|
1440
|
-
}
|
|
1441
|
-
rememberLastCliInput(targetDs, pendingRawInput ?? pendingPrompt, pendingRawInput ?? prompt);
|
|
1442
|
-
targetDs.pendingPrompt = undefined;
|
|
1443
|
-
targetDs.pendingAttachments = undefined;
|
|
1444
|
-
targetDs.pendingMentions = undefined;
|
|
1445
|
-
targetDs.pendingSender = undefined;
|
|
1446
|
-
targetDs.pendingFollowUps = undefined;
|
|
1447
|
-
forkWorker(targetDs, prompt);
|
|
1448
|
-
// A card click has no turn of its own — anchor the confirmation to the
|
|
1449
|
-
// session's current reply-target turn so a shared fold-back topic keeps
|
|
1450
|
-
// it in-thread (same leak as the /repo command path).
|
|
1451
|
-
await sessionReply(rootId, t('cmd.repo.selected_in_pending', { name: dirLabel }, locTarget), undefined, fallbackTurnId(targetDs, undefined));
|
|
1452
|
-
logger.info(`[${tag(targetDs)}] Repo selected: ${dirPath}, spawning CLI`);
|
|
1453
|
-
}
|
|
1454
|
-
else {
|
|
1455
|
-
// Mid-session repo switch — close old session, start fresh.
|
|
1456
|
-
killWorker(targetDs);
|
|
1457
|
-
// Park the current card in `frozenCards` so the next POST under the new
|
|
1458
|
-
// session sweeps it via recall. closeSession() wipes the on-disk
|
|
1459
|
-
// frozen-cards file under the OLD sessionId, but the in-memory Map
|
|
1460
|
-
// travels with `targetDs` into the new session and still carries the
|
|
1461
|
-
// old messageId for deletion. If fork or POST fails, the parked card
|
|
1462
|
-
// stays in the thread instead of vanishing prematurely.
|
|
1463
|
-
parkStreamCard(targetDs);
|
|
1464
|
-
sessionStore.closeSession(targetDs.session.sessionId);
|
|
1465
|
-
const session = sessionStore.createSession(targetDs.chatId, rootId, dirLabel, targetDs.chatType);
|
|
1466
|
-
targetDs.session = session;
|
|
1467
|
-
targetDs.lastUserPrompt = undefined;
|
|
1468
|
-
targetDs.lastCliInput = undefined;
|
|
1469
|
-
// Pin workingDir + larkAppId onto the new session before forkWorker.
|
|
1470
|
-
// Without this, a daemon restart restores the session with an empty
|
|
1471
|
-
// workingDir and the worker spawns in the bot's default cwd, so
|
|
1472
|
-
// `claude --resume` looks in the wrong .claude/projects/<hash>/ dir and
|
|
1473
|
-
// exits code 0 immediately, crash-looping until the rate-limiter trips.
|
|
1474
|
-
targetDs.session.workingDir = dirPath;
|
|
1475
|
-
targetDs.session.larkAppId = targetDs.larkAppId;
|
|
1476
|
-
sessionStore.updateSession(targetDs.session);
|
|
1477
|
-
targetDs.hasHistory = false;
|
|
1478
|
-
// Re-persist the parked card under the NEW sessionId so a daemon crash
|
|
1479
|
-
// before the next POST doesn't strand it. closeSession() above wiped
|
|
1480
|
-
// the on-disk file under the OLD sessionId; without this re-save, the
|
|
1481
|
-
// in-memory Map only survives in process memory.
|
|
1482
|
-
if (targetDs.frozenCards && targetDs.frozenCards.size > 0) {
|
|
1483
|
-
saveFrozenCards(targetDs.session.sessionId, targetDs.frozenCards);
|
|
1484
|
-
}
|
|
1485
|
-
// Drop the old turn's streaming-card reference so worker_ready POSTs a
|
|
1486
|
-
// fresh card for the new session instead of PATCHing the previous one.
|
|
1487
|
-
targetDs.streamCardId = undefined;
|
|
1488
|
-
targetDs.streamCardNonce = undefined;
|
|
1489
|
-
targetDs.streamCardPending = undefined;
|
|
1490
|
-
targetDs.lastScreenContent = undefined;
|
|
1491
|
-
targetDs.lastScreenStatus = undefined;
|
|
1492
|
-
forkWorker(targetDs, '', false);
|
|
1493
|
-
await sessionReply(rootId, t('cmd.repo.switched_to', { name: dirLabel }, locTarget));
|
|
1494
|
-
logger.info(`[${tag(targetDs)}] Repo switched to ${dirPath}, new session created`);
|
|
1495
|
-
}
|
|
1496
|
-
// Withdraw the repo selection card
|
|
1497
|
-
if (cardMessageId && larkAppId)
|
|
1498
|
-
deleteMessage(larkAppId, cardMessageId);
|
|
1499
|
-
targetDs.repoCardMessageId = undefined;
|
|
1500
|
-
};
|
|
1545
|
+
// Shared commit context for a resolved directory — funnels the dropdown,
|
|
1546
|
+
// worktree and manual-entry flows through the same module-level
|
|
1547
|
+
// commitRepoSelection (pin dir, then fork pending CLI or close+recreate).
|
|
1548
|
+
const commitCtx = { ds: targetDs, rootId, cardMessageId, larkAppId, activeSessions, sessionReply };
|
|
1501
1549
|
if (isWorktreeOpen) {
|
|
1502
1550
|
// Worktree creation involves a `git fetch` that can take many seconds —
|
|
1503
1551
|
// ack the card action immediately with a toast and finish asynchronously.
|
|
@@ -1544,7 +1592,9 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
1544
1592
|
if (sessionChanged())
|
|
1545
1593
|
return notSwitched(creation, 'during reply');
|
|
1546
1594
|
try {
|
|
1547
|
-
|
|
1595
|
+
// The "worktree 已创建:…" notice above already confirms the switch —
|
|
1596
|
+
// suppress commitRepoSelection's own "已选择/已切换" to avoid a dup.
|
|
1597
|
+
await commitRepoSelection(commitCtx, creation.path, `${pathBasename(creation.path)} (${creation.branch})`, { suppressConfirmReply: true });
|
|
1548
1598
|
}
|
|
1549
1599
|
catch (e) {
|
|
1550
1600
|
// The worktree DOES exist at this point — only the switch failed.
|
|
@@ -1567,6 +1617,6 @@ export async function handleCardAction(data, deps, larkAppId) {
|
|
|
1567
1617
|
if (targetDs.worktreeCreating) {
|
|
1568
1618
|
return { toast: { type: 'info', content: t('cmd.repo.worktree_in_progress', undefined, locTarget) } };
|
|
1569
1619
|
}
|
|
1570
|
-
await
|
|
1620
|
+
await commitRepoSelection(commitCtx, selectedPath, displayName);
|
|
1571
1621
|
}
|
|
1572
1622
|
//# sourceMappingURL=card-handler.js.map
|