@keyou007/agent-bot 0.1.15-alpha.0 → 0.1.15-alpha.2
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/CHANGELOG.md +14 -0
- package/README.md +23 -20
- package/README.zh.md +23 -20
- package/config.example.yaml +1 -0
- package/dist/cli/FeishuAppConfiguration.js +42 -3
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/feishu/CardRenderer.js +114 -16
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +21 -6
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuMarkdown.js +58 -6
- package/dist/feishu/FeishuMarkdown.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +33 -4
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +7 -6
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/FinalResponseBranding.js +15 -0
- package/dist/feishu/FinalResponseBranding.js.map +1 -0
- package/dist/logging/DailyLogStream.js +73 -0
- package/dist/logging/DailyLogStream.js.map +1 -0
- package/dist/logging/logger.js +2 -1
- package/dist/logging/logger.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +270 -40
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +126 -0
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/migrations.js +11 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/supervision/SupervisorDiagnostics.js +37 -21
- package/dist/supervision/SupervisorDiagnostics.js.map +1 -1
- package/dist/supervisor.js +11 -4
- package/dist/supervisor.js.map +1 -1
- package/dist/utils/executeShellCommand.js +83 -16
- package/dist/utils/executeShellCommand.js.map +1 -1
- package/docs/technical-reference.md +9 -8
- package/docs/technical-reference.zh.md +9 -8
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/cli/FeishuAppConfiguration.ts +76 -3
- package/src/cli.ts +6 -1
- package/src/feishu/CardRenderer.ts +150 -17
- package/src/feishu/FeishuConnector.ts +32 -7
- package/src/feishu/FeishuMarkdown.ts +64 -5
- package/src/feishu/FeishuMessageClient.ts +41 -12
- package/src/feishu/FeishuTurnPresenter.ts +9 -6
- package/src/feishu/FinalResponseBranding.ts +18 -0
- package/src/logging/DailyLogStream.ts +88 -0
- package/src/logging/logger.ts +2 -1
- package/src/proxy/ProxySessionController.ts +292 -39
- package/src/state/StateStore.ts +180 -0
- package/src/state/migrations.ts +11 -0
- package/src/supervision/SupervisorDiagnostics.ts +41 -25
- package/src/supervisor.ts +10 -4
- package/src/utils/executeShellCommand.ts +103 -15
|
@@ -6,12 +6,13 @@ import { resolveUserPath } from "../config/paths.js";
|
|
|
6
6
|
import { CommandRouter } from "../commands/CommandRouter.js";
|
|
7
7
|
import { baseChatContextKey, isThreadContextKey } from "../feishu/contextKey.js";
|
|
8
8
|
import { CardRenderer, } from "../feishu/CardRenderer.js";
|
|
9
|
+
import { CardUpdateScheduler } from "../feishu/CardUpdateScheduler.js";
|
|
9
10
|
import { generateGroupAvatarPng, resolveGroupAvatarProjectName } from "../feishu/GroupAvatarGenerator.js";
|
|
10
11
|
import { normalizeFeishuPostText } from "../feishu/InboundText.js";
|
|
11
12
|
import { allowsFeishuUser } from "../feishu/ownerAccess.js";
|
|
12
13
|
import { buildTurnGraphRows } from "../presentation/turnGraph.js";
|
|
13
14
|
import { createId } from "../utils/id.js";
|
|
14
|
-
import {
|
|
15
|
+
import { truncateMiddle, truncateText } from "../utils/markdown.js";
|
|
15
16
|
import { normalizeTaskTitle } from "../utils/taskTitle.js";
|
|
16
17
|
import { executeShellCommand, } from "../utils/executeShellCommand.js";
|
|
17
18
|
const MESSAGE_RECEIVED_REACTION = "OnIt";
|
|
@@ -24,6 +25,7 @@ const RESET_HISTORY_PAGE_SIZE = 10;
|
|
|
24
25
|
const MAX_LLM_TURN_RETRIES = 3;
|
|
25
26
|
const RECOVERY_ACTIVITY_WINDOW_MS = 5 * 60 * 1_000;
|
|
26
27
|
const RECOVERY_HEARTBEAT_INTERVAL_MS = 60 * 1_000;
|
|
28
|
+
const SHELL_COMMAND_ELAPSED_UPDATE_INTERVAL_MS = 3_000;
|
|
27
29
|
const FORWARD_ATTACHMENT_WINDOW_MS = 800;
|
|
28
30
|
const DEFAULT_MERGED_FORWARD_INSTRUCTION = "请参考以下内容回复用户";
|
|
29
31
|
const DEFAULT_REFERENCED_MESSAGE_INSTRUCTION = "请参考引用消息回复用户";
|
|
@@ -417,7 +419,7 @@ export class ProxySessionController {
|
|
|
417
419
|
if (isQueueIndependentCommand(command)) {
|
|
418
420
|
await this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
|
|
419
421
|
try {
|
|
420
|
-
if (command.type
|
|
422
|
+
if (command.type === "prompt")
|
|
421
423
|
await this.ensureThreadFork(message);
|
|
422
424
|
await this.execute(message.contextKey, command, message.messageId, replyTarget, localImagePaths, message.userId, message);
|
|
423
425
|
if (!isPromptCommand(command))
|
|
@@ -433,7 +435,7 @@ export class ProxySessionController {
|
|
|
433
435
|
const previous = this.messageQueues.get(message.contextKey) ?? Promise.resolve();
|
|
434
436
|
const next = previous.catch(() => undefined).then(() => this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
|
|
435
437
|
try {
|
|
436
|
-
if (command.type
|
|
438
|
+
if (command.type === "prompt")
|
|
437
439
|
await this.ensureThreadFork(message);
|
|
438
440
|
await this.execute(message.contextKey, command, message.messageId, replyTarget, localImagePaths, message.userId, message);
|
|
439
441
|
if (!isPromptCommand(command))
|
|
@@ -524,6 +526,25 @@ export class ProxySessionController {
|
|
|
524
526
|
await this.assertSendableFile(filePath);
|
|
525
527
|
await this.outbound.sendFile(contextKey, filePath);
|
|
526
528
|
}
|
|
529
|
+
else if (kind === "directory_new_folder_prompt") {
|
|
530
|
+
const directory = directoryActionPath(scopedAction.value.directory);
|
|
531
|
+
await this.assertBrowsableDirectory(directory);
|
|
532
|
+
await this.outbound.updateInteractiveCard(contextKey, requiredCardMessageId(scopedAction.messageId), this.cardRenderer.renderDirectoryNewFolderCard({
|
|
533
|
+
directory,
|
|
534
|
+
displayDirectory: abbreviateHomeDirectory(directory),
|
|
535
|
+
contextKey,
|
|
536
|
+
page: directoryPageValue(scopedAction.value.page),
|
|
537
|
+
}));
|
|
538
|
+
}
|
|
539
|
+
else if (kind === "directory_new_folder_submit") {
|
|
540
|
+
await this.createDirectoryFromCard(contextKey, scopedAction);
|
|
541
|
+
}
|
|
542
|
+
else if (kind === "directory_new_folder_cancel") {
|
|
543
|
+
await this.openDirectoryBrowser(contextKey, directoryActionPath(scopedAction.value.directory), {
|
|
544
|
+
updateMessageId: requiredCardMessageId(scopedAction.messageId),
|
|
545
|
+
page: directoryPageValue(scopedAction.value.page),
|
|
546
|
+
});
|
|
547
|
+
}
|
|
527
548
|
else if (kind === "directory_new" || kind === "directory_new_group") {
|
|
528
549
|
const directory = directoryActionPath(scopedAction.value.directory);
|
|
529
550
|
await this.assertBrowsableDirectory(directory);
|
|
@@ -1095,8 +1116,7 @@ export class ProxySessionController {
|
|
|
1095
1116
|
if (!session.remoteSessionId || !this.isCodexSession(session)) {
|
|
1096
1117
|
throw new Error("The task is not an App Server task that can be reset.");
|
|
1097
1118
|
}
|
|
1098
|
-
const
|
|
1099
|
-
const rows = this.store.listCompletedTurnGraph(session.localSessionId, contextKey);
|
|
1119
|
+
const rows = this.store.listTaskTurnGraph(session.localSessionId);
|
|
1100
1120
|
const graph = buildTurnGraphRows(rows.map((turn) => ({ turnId: turn.turnId, parentTurnId: turn.parentTurnId })));
|
|
1101
1121
|
return {
|
|
1102
1122
|
session,
|
|
@@ -1219,6 +1239,9 @@ export class ProxySessionController {
|
|
|
1219
1239
|
}
|
|
1220
1240
|
async execute(contextKey, command, messageId, replyTarget, localImagePaths, userId, incomingMessage) {
|
|
1221
1241
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
1242
|
+
if (!this.currentSession(contextKey) && isThreadContextKey(contextKey) && commandRequiresCurrentSession(command)) {
|
|
1243
|
+
throw new Error(unboundThreadTaskMessage());
|
|
1244
|
+
}
|
|
1222
1245
|
switch (command.type) {
|
|
1223
1246
|
case "shell":
|
|
1224
1247
|
await this.runShellCommand(contextKey, command.command);
|
|
@@ -2501,8 +2524,13 @@ export class ProxySessionController {
|
|
|
2501
2524
|
if (existing)
|
|
2502
2525
|
return { record, runtime, session: existing };
|
|
2503
2526
|
const pending = this.sessionLoads.get(record.localSessionId);
|
|
2504
|
-
if (pending)
|
|
2505
|
-
|
|
2527
|
+
if (pending) {
|
|
2528
|
+
const loaded = await pending;
|
|
2529
|
+
return {
|
|
2530
|
+
...loaded,
|
|
2531
|
+
record: { ...loaded.record, contextKey: record.contextKey },
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2506
2534
|
this.outbound.registerSession(record.localSessionId, record.contextKey, record.title, record.cwd, this.agentLabel(record.agentName));
|
|
2507
2535
|
const loading = (async () => {
|
|
2508
2536
|
if (record.lastTurnId) {
|
|
@@ -2566,7 +2594,11 @@ export class ProxySessionController {
|
|
|
2566
2594
|
}
|
|
2567
2595
|
this.persistRuntimeSession(record, session, session.activeTurnId ? "running" : "ready");
|
|
2568
2596
|
const saved = this.store.getSession(record.localSessionId) ?? record;
|
|
2569
|
-
return {
|
|
2597
|
+
return {
|
|
2598
|
+
record: { ...saved, contextKey: record.contextKey },
|
|
2599
|
+
runtime,
|
|
2600
|
+
session,
|
|
2601
|
+
};
|
|
2570
2602
|
})();
|
|
2571
2603
|
this.sessionLoads.set(record.localSessionId, loading);
|
|
2572
2604
|
try {
|
|
@@ -2653,6 +2685,7 @@ export class ProxySessionController {
|
|
|
2653
2685
|
presentationError = error;
|
|
2654
2686
|
}
|
|
2655
2687
|
if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
|
|
2688
|
+
await this.refreshGoalCards(event.sessionId);
|
|
2656
2689
|
const terminalStatus = event.type === "turn_completed"
|
|
2657
2690
|
? "completed"
|
|
2658
2691
|
: event.type === "turn_cancelled"
|
|
@@ -2994,8 +3027,10 @@ export class ProxySessionController {
|
|
|
2994
3027
|
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
2995
3028
|
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
2996
3029
|
}
|
|
2997
|
-
const
|
|
2998
|
-
|
|
3030
|
+
const historyTurn = this.store.listTaskTurnGraph(current.localSessionId)
|
|
3031
|
+
.find((turn) => turn.turnId === turnId);
|
|
3032
|
+
const snapshot = turnViewSnapshot(historyTurn?.snapshot);
|
|
3033
|
+
if (!snapshot || snapshot.status !== "completed") {
|
|
2999
3034
|
throw new Error("只能将当前任务 Reset 到已成功完成的轮次。");
|
|
3000
3035
|
}
|
|
3001
3036
|
if (current.lastTurnId === turnId && current.lastTurnStatus === "completed") {
|
|
@@ -3015,7 +3050,7 @@ export class ProxySessionController {
|
|
|
3015
3050
|
throw new Error("当前任务仍在执行。请等待任务完成或先停止任务,再点击 Reset。");
|
|
3016
3051
|
}
|
|
3017
3052
|
const origin = this.store.getTurnRuntimeOrigin(turnId);
|
|
3018
|
-
if (origin &&
|
|
3053
|
+
if (origin && origin.agentName !== current.agentName) {
|
|
3019
3054
|
throw new Error("这张思考卡片与当前任务不匹配,已拒绝 Reset。");
|
|
3020
3055
|
}
|
|
3021
3056
|
const sourceRemoteSessionId = origin?.remoteSessionId ?? current.remoteSessionId;
|
|
@@ -3383,6 +3418,21 @@ export class ProxySessionController {
|
|
|
3383
3418
|
await this.sendGoalCard(contextKey, goal, command.action === "edit" ? "Goal 已更新。" : "Goal 已启动,Agent 会持续执行直到完成、暂停或遇到阻塞。", record);
|
|
3384
3419
|
}
|
|
3385
3420
|
async sendGoalCard(contextKey, goal, notice, record = this.currentSession(contextKey)) {
|
|
3421
|
+
const card = this.renderGoalCard(goal, notice, record);
|
|
3422
|
+
if (!record) {
|
|
3423
|
+
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
const existing = this.store.getGoalCardDelivery(record.localSessionId, contextKey);
|
|
3427
|
+
if (existing) {
|
|
3428
|
+
await this.outbound.updateInteractiveCard(contextKey, existing.messageId, card);
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
const messageId = await this.outbound.sendInteractiveCard(contextKey, card);
|
|
3432
|
+
if (messageId)
|
|
3433
|
+
this.store.saveGoalCardDelivery(record.localSessionId, contextKey, messageId);
|
|
3434
|
+
}
|
|
3435
|
+
renderGoalCard(goal, notice, record) {
|
|
3386
3436
|
const sections = [];
|
|
3387
3437
|
if (notice)
|
|
3388
3438
|
sections.push({ lines: [cardText(notice)] });
|
|
@@ -3405,23 +3455,133 @@ export class ProxySessionController {
|
|
|
3405
3455
|
"**/goal edit <新目标>** 修改 **/goal clear** 清除",
|
|
3406
3456
|
],
|
|
3407
3457
|
});
|
|
3408
|
-
|
|
3458
|
+
const title = goal ? `Agent Goal · ${goalStatusLabel(goal.status)}` : "Agent Goal";
|
|
3459
|
+
return this.cardRenderer.renderSectionsCard(title, sections);
|
|
3460
|
+
}
|
|
3461
|
+
async refreshGoalCards(localSessionId) {
|
|
3462
|
+
const deliveries = this.store.listGoalCardDeliveries(localSessionId);
|
|
3463
|
+
if (deliveries.length === 0)
|
|
3464
|
+
return;
|
|
3465
|
+
const record = this.store.getSession(localSessionId);
|
|
3466
|
+
if (!record)
|
|
3467
|
+
return;
|
|
3468
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
3469
|
+
if (runtime.kind !== "codex" || !runtime.getGoal)
|
|
3470
|
+
return;
|
|
3471
|
+
try {
|
|
3472
|
+
const loaded = runtime.getSession(localSessionId) ?? (await this.loadSession(record)).session;
|
|
3473
|
+
const goal = await runtime.getGoal(loaded.localSessionId);
|
|
3474
|
+
const card = this.renderGoalCard(goal, undefined, record);
|
|
3475
|
+
await Promise.all(deliveries.map(async (delivery) => {
|
|
3476
|
+
try {
|
|
3477
|
+
await this.outbound.updateInteractiveCard(delivery.contextKey, delivery.messageId, card);
|
|
3478
|
+
}
|
|
3479
|
+
catch (error) {
|
|
3480
|
+
this.logger.warn({ error, localSessionId, contextKey: delivery.contextKey, messageId: delivery.messageId }, "Failed to refresh an Agent Goal card.");
|
|
3481
|
+
}
|
|
3482
|
+
}));
|
|
3483
|
+
}
|
|
3484
|
+
catch (error) {
|
|
3485
|
+
this.logger.warn({ error, localSessionId }, "Failed to read the latest Agent Goal status.");
|
|
3486
|
+
}
|
|
3409
3487
|
}
|
|
3410
3488
|
async runShellCommand(contextKey, command) {
|
|
3411
3489
|
const cwd = this.currentSession(contextKey)?.cwd ?? this.config.defaults.cwd;
|
|
3412
|
-
const
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3490
|
+
const startedAt = Date.now();
|
|
3491
|
+
let latestOutput = {
|
|
3492
|
+
stdout: "",
|
|
3493
|
+
stderr: "",
|
|
3494
|
+
outputTruncated: false,
|
|
3495
|
+
};
|
|
3496
|
+
let runningState = {
|
|
3497
|
+
command,
|
|
3498
|
+
cwd,
|
|
3499
|
+
...latestOutput,
|
|
3500
|
+
status: "running",
|
|
3501
|
+
elapsedMs: 0,
|
|
3502
|
+
};
|
|
3503
|
+
const cardMessageId = await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderShellCommandCard(runningState));
|
|
3504
|
+
const scheduler = cardMessageId
|
|
3505
|
+
? new CardUpdateScheduler({
|
|
3506
|
+
render: (state) => this.cardRenderer.renderShellCommandCard(state),
|
|
3507
|
+
write: (card) => this.outbound.updateInteractiveCard(contextKey, cardMessageId, card),
|
|
3508
|
+
onError: (error) => this.logger.warn({ error, contextKey, messageId: cardMessageId }, "Failed to update a shell command card."),
|
|
3509
|
+
})
|
|
3510
|
+
: undefined;
|
|
3511
|
+
scheduler?.seed(runningState);
|
|
3512
|
+
const elapsedTimer = scheduler ? setInterval(() => {
|
|
3513
|
+
runningState = { ...runningState, elapsedMs: Date.now() - startedAt };
|
|
3514
|
+
scheduler.update(runningState);
|
|
3515
|
+
}, SHELL_COMMAND_ELAPSED_UPDATE_INTERVAL_MS) : undefined;
|
|
3516
|
+
elapsedTimer?.unref?.();
|
|
3517
|
+
const updateRunningOutput = (snapshot) => {
|
|
3518
|
+
latestOutput = snapshot;
|
|
3519
|
+
runningState = {
|
|
3520
|
+
...runningState,
|
|
3521
|
+
...snapshot,
|
|
3522
|
+
elapsedMs: Date.now() - startedAt,
|
|
3523
|
+
};
|
|
3524
|
+
scheduler?.update(runningState);
|
|
3525
|
+
};
|
|
3526
|
+
let result;
|
|
3527
|
+
try {
|
|
3528
|
+
result = await this.shellCommandExecutor(command, cwd, { onOutput: updateRunningOutput });
|
|
3529
|
+
}
|
|
3530
|
+
catch (error) {
|
|
3531
|
+
if (elapsedTimer)
|
|
3532
|
+
clearInterval(elapsedTimer);
|
|
3533
|
+
const failedCard = this.cardRenderer.renderShellCommandCard({
|
|
3534
|
+
command,
|
|
3535
|
+
cwd,
|
|
3536
|
+
...latestOutput,
|
|
3537
|
+
stderr: [latestOutput.stderr.trimEnd(), runtimeErrorMessage(error)].filter(Boolean).join("\n"),
|
|
3538
|
+
status: "failed",
|
|
3539
|
+
elapsedMs: Date.now() - startedAt,
|
|
3540
|
+
});
|
|
3541
|
+
if (scheduler) {
|
|
3542
|
+
try {
|
|
3543
|
+
await scheduler.flush({
|
|
3544
|
+
...runningState,
|
|
3545
|
+
stderr: [latestOutput.stderr.trimEnd(), runtimeErrorMessage(error)].filter(Boolean).join("\n"),
|
|
3546
|
+
status: "failed",
|
|
3547
|
+
elapsedMs: Date.now() - startedAt,
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
catch (cardError) {
|
|
3551
|
+
this.logger.warn({ error: cardError, contextKey, messageId: cardMessageId }, "Failed to finalize a shell command card.");
|
|
3552
|
+
}
|
|
3553
|
+
finally {
|
|
3554
|
+
scheduler.dispose();
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
else {
|
|
3558
|
+
await this.outbound.sendInteractiveCard(contextKey, failedCard);
|
|
3559
|
+
}
|
|
3560
|
+
throw error;
|
|
3561
|
+
}
|
|
3562
|
+
if (elapsedTimer)
|
|
3563
|
+
clearInterval(elapsedTimer);
|
|
3564
|
+
const finalState = {
|
|
3565
|
+
command,
|
|
3566
|
+
cwd,
|
|
3567
|
+
stdout: result.stdout,
|
|
3568
|
+
stderr: result.stderr,
|
|
3569
|
+
status: result.timedOut ? "timed_out" : result.exitCode === 0 ? "completed" : "failed",
|
|
3570
|
+
exitCode: result.exitCode,
|
|
3571
|
+
elapsedMs: Date.now() - startedAt,
|
|
3572
|
+
outputTruncated: result.outputTruncated,
|
|
3573
|
+
};
|
|
3574
|
+
if (scheduler) {
|
|
3575
|
+
try {
|
|
3576
|
+
await scheduler.flush(finalState);
|
|
3577
|
+
}
|
|
3578
|
+
finally {
|
|
3579
|
+
scheduler.dispose();
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
else {
|
|
3583
|
+
await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderShellCommandCard(finalState));
|
|
3584
|
+
}
|
|
3425
3585
|
}
|
|
3426
3586
|
async setTitle(contextKey, title) {
|
|
3427
3587
|
const normalizedTitle = normalizeTaskTitle(title);
|
|
@@ -3532,7 +3692,7 @@ export class ProxySessionController {
|
|
|
3532
3692
|
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
3533
3693
|
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
3534
3694
|
}
|
|
3535
|
-
const allTurns = this.store.
|
|
3695
|
+
const allTurns = this.store.listTaskTurnGraph(current.localSessionId);
|
|
3536
3696
|
const graphRows = buildTurnGraphRows(allTurns.map((turn) => ({
|
|
3537
3697
|
turnId: turn.turnId,
|
|
3538
3698
|
parentTurnId: turn.parentTurnId,
|
|
@@ -3668,7 +3828,6 @@ export class ProxySessionController {
|
|
|
3668
3828
|
contextKey,
|
|
3669
3829
|
},
|
|
3670
3830
|
},
|
|
3671
|
-
actions: directoryCreationActions(entry.path, contextKey),
|
|
3672
3831
|
}
|
|
3673
3832
|
: {
|
|
3674
3833
|
name: entry.name,
|
|
@@ -3688,12 +3847,12 @@ export class ProxySessionController {
|
|
|
3688
3847
|
const card = this.cardRenderer.renderDirectoryBrowserCard({
|
|
3689
3848
|
directory: abbreviateHomeDirectory(directory),
|
|
3690
3849
|
entries: visibleEntries,
|
|
3691
|
-
currentActions: directoryCreationActions(directory, contextKey),
|
|
3850
|
+
currentActions: directoryCreationActions(directory, contextKey, page),
|
|
3692
3851
|
navigationActions,
|
|
3693
3852
|
footerLines: [
|
|
3694
3853
|
`第 ${page + 1}/${totalPages} 页 · ${directoryCount} 个目录 · ${entries.length - directoryCount} 个文件`,
|
|
3695
3854
|
"",
|
|
3696
|
-
">
|
|
3855
|
+
"> 点击目录名称进入,点击文件名称发送到当前会话;卡片顶部可新建目录、任务或群聊。",
|
|
3697
3856
|
],
|
|
3698
3857
|
});
|
|
3699
3858
|
if (options.updateMessageId) {
|
|
@@ -3722,7 +3881,6 @@ export class ProxySessionController {
|
|
|
3722
3881
|
contextKey,
|
|
3723
3882
|
},
|
|
3724
3883
|
},
|
|
3725
|
-
actions: directoryCreationActions(drive.root, contextKey),
|
|
3726
3884
|
}));
|
|
3727
3885
|
const card = this.cardRenderer.renderDirectoryBrowserCard({
|
|
3728
3886
|
directory: "此电脑",
|
|
@@ -3732,7 +3890,7 @@ export class ProxySessionController {
|
|
|
3732
3890
|
footerLines: [
|
|
3733
3891
|
`第 ${page + 1}/${totalPages} 页 · ${drives.length} 个磁盘`,
|
|
3734
3892
|
"",
|
|
3735
|
-
">
|
|
3893
|
+
"> 点击磁盘名称进入。",
|
|
3736
3894
|
],
|
|
3737
3895
|
});
|
|
3738
3896
|
if (options.updateMessageId) {
|
|
@@ -3753,6 +3911,28 @@ export class ProxySessionController {
|
|
|
3753
3911
|
if (!stats.isDirectory())
|
|
3754
3912
|
throw new Error(`这不是目录:${directory}`);
|
|
3755
3913
|
}
|
|
3914
|
+
async createDirectoryFromCard(contextKey, action) {
|
|
3915
|
+
const directory = directoryActionPath(action.value.directory);
|
|
3916
|
+
await this.assertBrowsableDirectory(directory);
|
|
3917
|
+
const folderName = directoryFolderName(cardFormValue(action.value, "folderName"));
|
|
3918
|
+
const newDirectory = path.join(directory, folderName);
|
|
3919
|
+
try {
|
|
3920
|
+
await fs.mkdir(newDirectory);
|
|
3921
|
+
}
|
|
3922
|
+
catch (error) {
|
|
3923
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
3924
|
+
? String(error.code)
|
|
3925
|
+
: undefined;
|
|
3926
|
+
if (code === "EEXIST")
|
|
3927
|
+
throw new Error(`目录已存在:${newDirectory}`);
|
|
3928
|
+
throw new Error(`无法创建目录 ${newDirectory}:${runtimeErrorMessage(error)}`);
|
|
3929
|
+
}
|
|
3930
|
+
await this.openDirectoryBrowser(contextKey, directory, {
|
|
3931
|
+
updateMessageId: requiredCardMessageId(action.messageId),
|
|
3932
|
+
page: directoryPageValue(action.value.page),
|
|
3933
|
+
});
|
|
3934
|
+
await this.outbound.sendText(contextKey, `已创建目录:${newDirectory}`);
|
|
3935
|
+
}
|
|
3756
3936
|
async assertSendableFile(filePath) {
|
|
3757
3937
|
let stats;
|
|
3758
3938
|
try {
|
|
@@ -4350,7 +4530,9 @@ export class ProxySessionController {
|
|
|
4350
4530
|
let activeTurnId;
|
|
4351
4531
|
let queued = 0;
|
|
4352
4532
|
if (!current) {
|
|
4353
|
-
taskLines.push(
|
|
4533
|
+
taskLines.push(isThreadContextKey(contextKey)
|
|
4534
|
+
? unboundThreadTaskMessage()
|
|
4535
|
+
: "无。直接发送消息即可创建一个未指定项目的 Agent 任务。");
|
|
4354
4536
|
}
|
|
4355
4537
|
else {
|
|
4356
4538
|
const agent = this.ensureAgent(current.agentName);
|
|
@@ -4581,8 +4763,11 @@ export class ProxySessionController {
|
|
|
4581
4763
|
}
|
|
4582
4764
|
requireCurrentSession(contextKey) {
|
|
4583
4765
|
const record = this.currentSession(contextKey);
|
|
4584
|
-
if (!record)
|
|
4585
|
-
throw new Error(
|
|
4766
|
+
if (!record) {
|
|
4767
|
+
throw new Error(isThreadContextKey(contextKey)
|
|
4768
|
+
? unboundThreadTaskMessage()
|
|
4769
|
+
: "当前没有任务,直接发送一条消息即可自动创建。");
|
|
4770
|
+
}
|
|
4586
4771
|
return record;
|
|
4587
4772
|
}
|
|
4588
4773
|
requireSession(contextKey, sessionId) {
|
|
@@ -5050,6 +5235,19 @@ function isQueueIndependentCommand(command) {
|
|
|
5050
5235
|
return true;
|
|
5051
5236
|
return false;
|
|
5052
5237
|
}
|
|
5238
|
+
function commandRequiresCurrentSession(command) {
|
|
5239
|
+
if (["dismiss", "stop", "title", "turns", "model", "provider", "thinking", "permissions", "nosteer"].includes(command.type)) {
|
|
5240
|
+
return true;
|
|
5241
|
+
}
|
|
5242
|
+
if (command.type === "archive" || command.type === "fork")
|
|
5243
|
+
return command.sessionId === undefined;
|
|
5244
|
+
if (command.type === "goal")
|
|
5245
|
+
return command.action !== "set";
|
|
5246
|
+
return false;
|
|
5247
|
+
}
|
|
5248
|
+
function unboundThreadTaskMessage() {
|
|
5249
|
+
return "当前话题尚未绑定任务。发送普通消息后,Agent Bot 会从可识别的原始轮次创建分支;没有原始轮次时会创建全新任务。也可以使用 /new 创建全新任务,或使用 /sessions 绑定现有任务。";
|
|
5250
|
+
}
|
|
5053
5251
|
function parseSessionPage(value) {
|
|
5054
5252
|
const parsed = typeof value === "string" || typeof value === "number" ? Number(value) : Number.NaN;
|
|
5055
5253
|
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
@@ -5085,10 +5283,43 @@ function directoryFileActionPath(value) {
|
|
|
5085
5283
|
}
|
|
5086
5284
|
return path.resolve(value);
|
|
5087
5285
|
}
|
|
5088
|
-
function
|
|
5286
|
+
function cardFormValue(value, name) {
|
|
5287
|
+
const formValue = value.formValue;
|
|
5288
|
+
return typeof formValue === "object" && formValue !== null
|
|
5289
|
+
? formValue[name]
|
|
5290
|
+
: undefined;
|
|
5291
|
+
}
|
|
5292
|
+
function directoryFolderName(value) {
|
|
5293
|
+
if (typeof value !== "string" || !value.trim())
|
|
5294
|
+
throw new Error("请输入目录名。");
|
|
5295
|
+
const name = value.trim();
|
|
5296
|
+
if (name === "." || name === "..")
|
|
5297
|
+
throw new Error("目录名不能是 . 或 ..。");
|
|
5298
|
+
if (name.length > 255)
|
|
5299
|
+
throw new Error("目录名不能超过 255 个字符。");
|
|
5300
|
+
if (/[<>:"/\\|?*\u0000-\u001F]/u.test(name)) {
|
|
5301
|
+
throw new Error("目录名不能包含路径分隔符或以下字符:< > : \" / \\ | ? *");
|
|
5302
|
+
}
|
|
5303
|
+
if (/[. ]$/u.test(name))
|
|
5304
|
+
throw new Error("目录名不能以空格或句点结尾。");
|
|
5305
|
+
if (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/iu.test(name)) {
|
|
5306
|
+
throw new Error("该名称是系统保留名称,请使用其他目录名。");
|
|
5307
|
+
}
|
|
5308
|
+
return name;
|
|
5309
|
+
}
|
|
5310
|
+
function directoryCreationActions(directory, contextKey, page) {
|
|
5089
5311
|
return [
|
|
5090
5312
|
{
|
|
5091
|
-
text: "
|
|
5313
|
+
text: "NewFolder",
|
|
5314
|
+
value: {
|
|
5315
|
+
action: "directory_new_folder_prompt",
|
|
5316
|
+
directory,
|
|
5317
|
+
contextKey,
|
|
5318
|
+
page: String(page),
|
|
5319
|
+
},
|
|
5320
|
+
},
|
|
5321
|
+
{
|
|
5322
|
+
text: "NewTask",
|
|
5092
5323
|
value: {
|
|
5093
5324
|
action: "directory_new",
|
|
5094
5325
|
directory,
|
|
@@ -5096,7 +5327,7 @@ function directoryCreationActions(directory, contextKey) {
|
|
|
5096
5327
|
},
|
|
5097
5328
|
},
|
|
5098
5329
|
{
|
|
5099
|
-
text: "
|
|
5330
|
+
text: "NewGroupTask",
|
|
5100
5331
|
value: {
|
|
5101
5332
|
action: "directory_new_group",
|
|
5102
5333
|
directory,
|
|
@@ -5348,14 +5579,13 @@ const FEISHU_GROUP_NAME_MAX_LENGTH = 60;
|
|
|
5348
5579
|
const GROUP_PROJECT_DIRECTORY_MAX_LENGTH = 15;
|
|
5349
5580
|
function formatGroupProjectDirectory(projectCwd) {
|
|
5350
5581
|
const value = abbreviateHomeDirectory(projectCwd);
|
|
5351
|
-
if (Array.from(value).length <= GROUP_PROJECT_DIRECTORY_MAX_LENGTH)
|
|
5352
|
-
return `[${value}]`;
|
|
5353
5582
|
const levels = value
|
|
5354
5583
|
.replace(/[\\/]+$/, "")
|
|
5355
5584
|
.split(/[\\/]+/)
|
|
5356
5585
|
.filter(Boolean)
|
|
5357
5586
|
.slice(-2);
|
|
5358
|
-
|
|
5587
|
+
const separator = value.includes("\\") ? path.win32.sep : path.posix.sep;
|
|
5588
|
+
return `[${fitTrailingPathLevels(levels, GROUP_PROJECT_DIRECTORY_MAX_LENGTH, separator)}]`;
|
|
5359
5589
|
}
|
|
5360
5590
|
function abbreviateHomeDirectory(value) {
|
|
5361
5591
|
const homeDirectory = os.homedir();
|