@keyou007/agent-bot 0.1.15-alpha.0 → 0.1.15-alpha.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/CHANGELOG.md +10 -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 +51 -8
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +17 -5
- 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 +192 -36
- 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 +75 -9
- package/src/feishu/FeishuConnector.ts +28 -6
- 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 +208 -35
- 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
|
@@ -24,8 +24,10 @@ import {
|
|
|
24
24
|
type ResetHistoryCardEntry,
|
|
25
25
|
type SessionTaskCardEntry,
|
|
26
26
|
type SessionTaskCardGroup,
|
|
27
|
+
type ShellCommandCardView,
|
|
27
28
|
type TaskListCardAction,
|
|
28
29
|
} from "../feishu/CardRenderer.js";
|
|
30
|
+
import { CardUpdateScheduler } from "../feishu/CardUpdateScheduler.js";
|
|
29
31
|
import { generateGroupAvatarPng, resolveGroupAvatarProjectName } from "../feishu/GroupAvatarGenerator.js";
|
|
30
32
|
import { normalizeFeishuPostText } from "../feishu/InboundText.js";
|
|
31
33
|
import { allowsFeishuUser } from "../feishu/ownerAccess.js";
|
|
@@ -54,10 +56,12 @@ import {
|
|
|
54
56
|
type TurnAnchorRecord,
|
|
55
57
|
} from "../state/StateStore.js";
|
|
56
58
|
import { createId } from "../utils/id.js";
|
|
57
|
-
import {
|
|
59
|
+
import { truncateMiddle, truncateText } from "../utils/markdown.js";
|
|
58
60
|
import { normalizeTaskTitle } from "../utils/taskTitle.js";
|
|
59
61
|
import {
|
|
60
62
|
executeShellCommand,
|
|
63
|
+
type ShellCommandOptions,
|
|
64
|
+
type ShellCommandOutputSnapshot,
|
|
61
65
|
type ShellCommandResult,
|
|
62
66
|
} from "../utils/executeShellCommand.js";
|
|
63
67
|
|
|
@@ -98,6 +102,7 @@ const RESET_HISTORY_PAGE_SIZE = 10;
|
|
|
98
102
|
const MAX_LLM_TURN_RETRIES = 3;
|
|
99
103
|
const RECOVERY_ACTIVITY_WINDOW_MS = 5 * 60 * 1_000;
|
|
100
104
|
const RECOVERY_HEARTBEAT_INTERVAL_MS = 60 * 1_000;
|
|
105
|
+
const SHELL_COMMAND_ELAPSED_UPDATE_INTERVAL_MS = 3_000;
|
|
101
106
|
const FORWARD_ATTACHMENT_WINDOW_MS = 800;
|
|
102
107
|
const DEFAULT_MERGED_FORWARD_INSTRUCTION = "请参考以下内容回复用户";
|
|
103
108
|
const DEFAULT_REFERENCED_MESSAGE_INSTRUCTION = "请参考引用消息回复用户";
|
|
@@ -366,7 +371,11 @@ export interface ProxyLifecycle {
|
|
|
366
371
|
rememberFeishuUserOpenId?(userOpenId: string): Promise<void> | void;
|
|
367
372
|
}
|
|
368
373
|
|
|
369
|
-
export type ShellCommandExecutor = (
|
|
374
|
+
export type ShellCommandExecutor = (
|
|
375
|
+
command: string,
|
|
376
|
+
cwd: string,
|
|
377
|
+
options?: ShellCommandOptions,
|
|
378
|
+
) => Promise<ShellCommandResult>;
|
|
370
379
|
export interface WindowsDriveInfo {
|
|
371
380
|
root: string;
|
|
372
381
|
label?: string;
|
|
@@ -686,7 +695,7 @@ export class ProxySessionController {
|
|
|
686
695
|
if (isQueueIndependentCommand(command)) {
|
|
687
696
|
await this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
|
|
688
697
|
try {
|
|
689
|
-
if (command.type
|
|
698
|
+
if (command.type === "prompt") await this.ensureThreadFork(message);
|
|
690
699
|
await this.execute(
|
|
691
700
|
message.contextKey,
|
|
692
701
|
command,
|
|
@@ -709,7 +718,7 @@ export class ProxySessionController {
|
|
|
709
718
|
const next = previous.catch(() => undefined).then(() =>
|
|
710
719
|
this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
|
|
711
720
|
try {
|
|
712
|
-
if (command.type
|
|
721
|
+
if (command.type === "prompt") await this.ensureThreadFork(message);
|
|
713
722
|
await this.execute(
|
|
714
723
|
message.contextKey,
|
|
715
724
|
command,
|
|
@@ -1488,8 +1497,7 @@ export class ProxySessionController {
|
|
|
1488
1497
|
if (!session.remoteSessionId || !this.isCodexSession(session)) {
|
|
1489
1498
|
throw new Error("The task is not an App Server task that can be reset.");
|
|
1490
1499
|
}
|
|
1491
|
-
const
|
|
1492
|
-
const rows = this.store.listCompletedTurnGraph(session.localSessionId, contextKey);
|
|
1500
|
+
const rows = this.store.listTaskTurnGraph(session.localSessionId);
|
|
1493
1501
|
const graph = buildTurnGraphRows(rows.map((turn) => ({ turnId: turn.turnId, parentTurnId: turn.parentTurnId })));
|
|
1494
1502
|
return {
|
|
1495
1503
|
session,
|
|
@@ -1641,6 +1649,9 @@ export class ProxySessionController {
|
|
|
1641
1649
|
incomingMessage?: IncomingMessage,
|
|
1642
1650
|
): Promise<void> {
|
|
1643
1651
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
1652
|
+
if (!this.currentSession(contextKey) && isThreadContextKey(contextKey) && commandRequiresCurrentSession(command)) {
|
|
1653
|
+
throw new Error(unboundThreadTaskMessage());
|
|
1654
|
+
}
|
|
1644
1655
|
switch (command.type) {
|
|
1645
1656
|
case "shell":
|
|
1646
1657
|
await this.runShellCommand(contextKey, command.command);
|
|
@@ -3308,7 +3319,13 @@ export class ProxySessionController {
|
|
|
3308
3319
|
const existing = runtime.getSession(record.localSessionId);
|
|
3309
3320
|
if (existing) return { record, runtime, session: existing };
|
|
3310
3321
|
const pending = this.sessionLoads.get(record.localSessionId);
|
|
3311
|
-
if (pending)
|
|
3322
|
+
if (pending) {
|
|
3323
|
+
const loaded = await pending;
|
|
3324
|
+
return {
|
|
3325
|
+
...loaded,
|
|
3326
|
+
record: { ...loaded.record, contextKey: record.contextKey },
|
|
3327
|
+
};
|
|
3328
|
+
}
|
|
3312
3329
|
|
|
3313
3330
|
this.outbound.registerSession(
|
|
3314
3331
|
record.localSessionId,
|
|
@@ -3377,7 +3394,11 @@ export class ProxySessionController {
|
|
|
3377
3394
|
}
|
|
3378
3395
|
this.persistRuntimeSession(record, session, session.activeTurnId ? "running" : "ready");
|
|
3379
3396
|
const saved = this.store.getSession(record.localSessionId) ?? record;
|
|
3380
|
-
return {
|
|
3397
|
+
return {
|
|
3398
|
+
record: { ...saved, contextKey: record.contextKey },
|
|
3399
|
+
runtime,
|
|
3400
|
+
session,
|
|
3401
|
+
};
|
|
3381
3402
|
})();
|
|
3382
3403
|
this.sessionLoads.set(record.localSessionId, loading);
|
|
3383
3404
|
try {
|
|
@@ -3476,6 +3497,7 @@ export class ProxySessionController {
|
|
|
3476
3497
|
}
|
|
3477
3498
|
|
|
3478
3499
|
if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
|
|
3500
|
+
await this.refreshGoalCards(event.sessionId);
|
|
3479
3501
|
const terminalStatus = event.type === "turn_completed"
|
|
3480
3502
|
? "completed"
|
|
3481
3503
|
: event.type === "turn_cancelled"
|
|
@@ -3883,8 +3905,10 @@ export class ProxySessionController {
|
|
|
3883
3905
|
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
3884
3906
|
}
|
|
3885
3907
|
|
|
3886
|
-
const
|
|
3887
|
-
|
|
3908
|
+
const historyTurn = this.store.listTaskTurnGraph(current.localSessionId)
|
|
3909
|
+
.find((turn) => turn.turnId === turnId);
|
|
3910
|
+
const snapshot = turnViewSnapshot(historyTurn?.snapshot);
|
|
3911
|
+
if (!snapshot || snapshot.status !== "completed") {
|
|
3888
3912
|
throw new Error("只能将当前任务 Reset 到已成功完成的轮次。");
|
|
3889
3913
|
}
|
|
3890
3914
|
if (current.lastTurnId === turnId && current.lastTurnStatus === "completed") {
|
|
@@ -3905,7 +3929,7 @@ export class ProxySessionController {
|
|
|
3905
3929
|
}
|
|
3906
3930
|
|
|
3907
3931
|
const origin = this.store.getTurnRuntimeOrigin(turnId);
|
|
3908
|
-
if (origin &&
|
|
3932
|
+
if (origin && origin.agentName !== current.agentName) {
|
|
3909
3933
|
throw new Error("这张思考卡片与当前任务不匹配,已拒绝 Reset。");
|
|
3910
3934
|
}
|
|
3911
3935
|
const sourceRemoteSessionId = origin?.remoteSessionId ?? current.remoteSessionId;
|
|
@@ -4369,6 +4393,25 @@ export class ProxySessionController {
|
|
|
4369
4393
|
notice?: string,
|
|
4370
4394
|
record = this.currentSession(contextKey),
|
|
4371
4395
|
): Promise<void> {
|
|
4396
|
+
const card = this.renderGoalCard(goal, notice, record);
|
|
4397
|
+
if (!record) {
|
|
4398
|
+
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
4399
|
+
return;
|
|
4400
|
+
}
|
|
4401
|
+
const existing = this.store.getGoalCardDelivery(record.localSessionId, contextKey);
|
|
4402
|
+
if (existing) {
|
|
4403
|
+
await this.outbound.updateInteractiveCard(contextKey, existing.messageId, card);
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
const messageId = await this.outbound.sendInteractiveCard(contextKey, card);
|
|
4407
|
+
if (messageId) this.store.saveGoalCardDelivery(record.localSessionId, contextKey, messageId);
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
private renderGoalCard(
|
|
4411
|
+
goal: RuntimeGoal | undefined,
|
|
4412
|
+
notice?: string,
|
|
4413
|
+
record?: SessionRecord,
|
|
4414
|
+
): Record<string, unknown> {
|
|
4372
4415
|
const sections: CardSection[] = [];
|
|
4373
4416
|
if (notice) sections.push({ lines: [cardText(notice)] });
|
|
4374
4417
|
sections.push(goal
|
|
@@ -4390,24 +4433,138 @@ export class ProxySessionController {
|
|
|
4390
4433
|
"**/goal edit <新目标>** 修改 **/goal clear** 清除",
|
|
4391
4434
|
],
|
|
4392
4435
|
});
|
|
4393
|
-
|
|
4436
|
+
const title = goal ? `Agent Goal · ${goalStatusLabel(goal.status)}` : "Agent Goal";
|
|
4437
|
+
return this.cardRenderer.renderSectionsCard(title, sections);
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4440
|
+
private async refreshGoalCards(localSessionId: string): Promise<void> {
|
|
4441
|
+
const deliveries = this.store.listGoalCardDeliveries(localSessionId);
|
|
4442
|
+
if (deliveries.length === 0) return;
|
|
4443
|
+
const record = this.store.getSession(localSessionId);
|
|
4444
|
+
if (!record) return;
|
|
4445
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
4446
|
+
if (runtime.kind !== "codex" || !runtime.getGoal) return;
|
|
4447
|
+
|
|
4448
|
+
try {
|
|
4449
|
+
const loaded = runtime.getSession(localSessionId) ?? (await this.loadSession(record)).session;
|
|
4450
|
+
const goal = await runtime.getGoal(loaded.localSessionId);
|
|
4451
|
+
const card = this.renderGoalCard(goal, undefined, record);
|
|
4452
|
+
await Promise.all(deliveries.map(async (delivery) => {
|
|
4453
|
+
try {
|
|
4454
|
+
await this.outbound.updateInteractiveCard(delivery.contextKey, delivery.messageId, card);
|
|
4455
|
+
} catch (error) {
|
|
4456
|
+
this.logger.warn(
|
|
4457
|
+
{ error, localSessionId, contextKey: delivery.contextKey, messageId: delivery.messageId },
|
|
4458
|
+
"Failed to refresh an Agent Goal card.",
|
|
4459
|
+
);
|
|
4460
|
+
}
|
|
4461
|
+
}));
|
|
4462
|
+
} catch (error) {
|
|
4463
|
+
this.logger.warn({ error, localSessionId }, "Failed to read the latest Agent Goal status.");
|
|
4464
|
+
}
|
|
4394
4465
|
}
|
|
4395
4466
|
|
|
4396
4467
|
private async runShellCommand(contextKey: string, command: string): Promise<void> {
|
|
4397
4468
|
const cwd = this.currentSession(contextKey)?.cwd ?? this.config.defaults.cwd;
|
|
4398
|
-
const
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4469
|
+
const startedAt = Date.now();
|
|
4470
|
+
let latestOutput: ShellCommandOutputSnapshot = {
|
|
4471
|
+
stdout: "",
|
|
4472
|
+
stderr: "",
|
|
4473
|
+
outputTruncated: false,
|
|
4474
|
+
};
|
|
4475
|
+
let runningState: ShellCommandCardView = {
|
|
4476
|
+
command,
|
|
4477
|
+
cwd,
|
|
4478
|
+
...latestOutput,
|
|
4479
|
+
status: "running",
|
|
4480
|
+
elapsedMs: 0,
|
|
4481
|
+
};
|
|
4482
|
+
const cardMessageId = await this.outbound.sendInteractiveCard(
|
|
4483
|
+
contextKey,
|
|
4484
|
+
this.cardRenderer.renderShellCommandCard(runningState),
|
|
4485
|
+
);
|
|
4486
|
+
const scheduler = cardMessageId
|
|
4487
|
+
? new CardUpdateScheduler<ShellCommandCardView>({
|
|
4488
|
+
render: (state) => this.cardRenderer.renderShellCommandCard(state),
|
|
4489
|
+
write: (card) => this.outbound.updateInteractiveCard(contextKey, cardMessageId, card),
|
|
4490
|
+
onError: (error) => this.logger.warn(
|
|
4491
|
+
{ error, contextKey, messageId: cardMessageId },
|
|
4492
|
+
"Failed to update a shell command card.",
|
|
4493
|
+
),
|
|
4494
|
+
})
|
|
4495
|
+
: undefined;
|
|
4496
|
+
scheduler?.seed(runningState);
|
|
4497
|
+
const elapsedTimer = scheduler ? setInterval(() => {
|
|
4498
|
+
runningState = { ...runningState, elapsedMs: Date.now() - startedAt };
|
|
4499
|
+
scheduler.update(runningState);
|
|
4500
|
+
}, SHELL_COMMAND_ELAPSED_UPDATE_INTERVAL_MS) : undefined;
|
|
4501
|
+
elapsedTimer?.unref?.();
|
|
4502
|
+
|
|
4503
|
+
const updateRunningOutput = (snapshot: ShellCommandOutputSnapshot): void => {
|
|
4504
|
+
latestOutput = snapshot;
|
|
4505
|
+
runningState = {
|
|
4506
|
+
...runningState,
|
|
4507
|
+
...snapshot,
|
|
4508
|
+
elapsedMs: Date.now() - startedAt,
|
|
4509
|
+
};
|
|
4510
|
+
scheduler?.update(runningState);
|
|
4511
|
+
};
|
|
4512
|
+
|
|
4513
|
+
let result: ShellCommandResult;
|
|
4514
|
+
try {
|
|
4515
|
+
result = await this.shellCommandExecutor(command, cwd, { onOutput: updateRunningOutput });
|
|
4516
|
+
} catch (error) {
|
|
4517
|
+
if (elapsedTimer) clearInterval(elapsedTimer);
|
|
4518
|
+
const failedCard = this.cardRenderer.renderShellCommandCard({
|
|
4519
|
+
command,
|
|
4520
|
+
cwd,
|
|
4521
|
+
...latestOutput,
|
|
4522
|
+
stderr: [latestOutput.stderr.trimEnd(), runtimeErrorMessage(error)].filter(Boolean).join("\n"),
|
|
4523
|
+
status: "failed",
|
|
4524
|
+
elapsedMs: Date.now() - startedAt,
|
|
4525
|
+
});
|
|
4526
|
+
if (scheduler) {
|
|
4527
|
+
try {
|
|
4528
|
+
await scheduler.flush({
|
|
4529
|
+
...runningState,
|
|
4530
|
+
stderr: [latestOutput.stderr.trimEnd(), runtimeErrorMessage(error)].filter(Boolean).join("\n"),
|
|
4531
|
+
status: "failed",
|
|
4532
|
+
elapsedMs: Date.now() - startedAt,
|
|
4533
|
+
});
|
|
4534
|
+
} catch (cardError) {
|
|
4535
|
+
this.logger.warn(
|
|
4536
|
+
{ error: cardError, contextKey, messageId: cardMessageId },
|
|
4537
|
+
"Failed to finalize a shell command card.",
|
|
4538
|
+
);
|
|
4539
|
+
} finally {
|
|
4540
|
+
scheduler.dispose();
|
|
4541
|
+
}
|
|
4542
|
+
} else {
|
|
4543
|
+
await this.outbound.sendInteractiveCard(contextKey, failedCard);
|
|
4544
|
+
}
|
|
4545
|
+
throw error;
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
if (elapsedTimer) clearInterval(elapsedTimer);
|
|
4549
|
+
const finalState: ShellCommandCardView = {
|
|
4550
|
+
command,
|
|
4551
|
+
cwd,
|
|
4552
|
+
stdout: result.stdout,
|
|
4553
|
+
stderr: result.stderr,
|
|
4554
|
+
status: result.timedOut ? "timed_out" : result.exitCode === 0 ? "completed" : "failed",
|
|
4555
|
+
exitCode: result.exitCode,
|
|
4556
|
+
elapsedMs: Date.now() - startedAt,
|
|
4557
|
+
outputTruncated: result.outputTruncated,
|
|
4558
|
+
};
|
|
4559
|
+
if (scheduler) {
|
|
4560
|
+
try {
|
|
4561
|
+
await scheduler.flush(finalState);
|
|
4562
|
+
} finally {
|
|
4563
|
+
scheduler.dispose();
|
|
4564
|
+
}
|
|
4565
|
+
} else {
|
|
4566
|
+
await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderShellCommandCard(finalState));
|
|
4567
|
+
}
|
|
4411
4568
|
}
|
|
4412
4569
|
|
|
4413
4570
|
private async setTitle(contextKey: string, title: string): Promise<void> {
|
|
@@ -4524,7 +4681,7 @@ export class ProxySessionController {
|
|
|
4524
4681
|
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
4525
4682
|
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
4526
4683
|
}
|
|
4527
|
-
const allTurns = this.store.
|
|
4684
|
+
const allTurns = this.store.listTaskTurnGraph(current.localSessionId);
|
|
4528
4685
|
const graphRows = buildTurnGraphRows(allTurns.map((turn) => ({
|
|
4529
4686
|
turnId: turn.turnId,
|
|
4530
4687
|
parentTurnId: turn.parentTurnId,
|
|
@@ -4666,7 +4823,6 @@ export class ProxySessionController {
|
|
|
4666
4823
|
contextKey,
|
|
4667
4824
|
},
|
|
4668
4825
|
},
|
|
4669
|
-
actions: directoryCreationActions(entry.path, contextKey),
|
|
4670
4826
|
}
|
|
4671
4827
|
: {
|
|
4672
4828
|
name: entry.name,
|
|
@@ -4691,7 +4847,7 @@ export class ProxySessionController {
|
|
|
4691
4847
|
footerLines: [
|
|
4692
4848
|
`第 ${page + 1}/${totalPages} 页 · ${directoryCount} 个目录 · ${entries.length - directoryCount} 个文件`,
|
|
4693
4849
|
"",
|
|
4694
|
-
">
|
|
4850
|
+
"> 点击目录名称进入,点击文件名称发送到当前会话;卡片顶部的 **New** 和 **NewGroup** 使用当前目录。",
|
|
4695
4851
|
],
|
|
4696
4852
|
});
|
|
4697
4853
|
if (options.updateMessageId) {
|
|
@@ -4723,7 +4879,6 @@ export class ProxySessionController {
|
|
|
4723
4879
|
contextKey,
|
|
4724
4880
|
},
|
|
4725
4881
|
},
|
|
4726
|
-
actions: directoryCreationActions(drive.root, contextKey),
|
|
4727
4882
|
}));
|
|
4728
4883
|
const card = this.cardRenderer.renderDirectoryBrowserCard({
|
|
4729
4884
|
directory: "此电脑",
|
|
@@ -4738,7 +4893,7 @@ export class ProxySessionController {
|
|
|
4738
4893
|
footerLines: [
|
|
4739
4894
|
`第 ${page + 1}/${totalPages} 页 · ${drives.length} 个磁盘`,
|
|
4740
4895
|
"",
|
|
4741
|
-
">
|
|
4896
|
+
"> 点击磁盘名称进入。",
|
|
4742
4897
|
],
|
|
4743
4898
|
});
|
|
4744
4899
|
if (options.updateMessageId) {
|
|
@@ -5420,7 +5575,9 @@ export class ProxySessionController {
|
|
|
5420
5575
|
let activeTurnId: string | undefined;
|
|
5421
5576
|
let queued = 0;
|
|
5422
5577
|
if (!current) {
|
|
5423
|
-
taskLines.push(
|
|
5578
|
+
taskLines.push(isThreadContextKey(contextKey)
|
|
5579
|
+
? unboundThreadTaskMessage()
|
|
5580
|
+
: "无。直接发送消息即可创建一个未指定项目的 Agent 任务。");
|
|
5424
5581
|
} else {
|
|
5425
5582
|
const agent = this.ensureAgent(current.agentName);
|
|
5426
5583
|
const runtimeSession = this.runtimes.forAgent(current.agentName).getSession(current.localSessionId);
|
|
@@ -5696,7 +5853,11 @@ export class ProxySessionController {
|
|
|
5696
5853
|
|
|
5697
5854
|
private requireCurrentSession(contextKey: string): SessionRecord {
|
|
5698
5855
|
const record = this.currentSession(contextKey);
|
|
5699
|
-
if (!record)
|
|
5856
|
+
if (!record) {
|
|
5857
|
+
throw new Error(isThreadContextKey(contextKey)
|
|
5858
|
+
? unboundThreadTaskMessage()
|
|
5859
|
+
: "当前没有任务,直接发送一条消息即可自动创建。");
|
|
5860
|
+
}
|
|
5700
5861
|
return record;
|
|
5701
5862
|
}
|
|
5702
5863
|
|
|
@@ -6213,6 +6374,19 @@ function isQueueIndependentCommand(command: Command): boolean {
|
|
|
6213
6374
|
return false;
|
|
6214
6375
|
}
|
|
6215
6376
|
|
|
6377
|
+
function commandRequiresCurrentSession(command: Command): boolean {
|
|
6378
|
+
if (["dismiss", "stop", "title", "turns", "model", "provider", "thinking", "permissions", "nosteer"].includes(command.type)) {
|
|
6379
|
+
return true;
|
|
6380
|
+
}
|
|
6381
|
+
if (command.type === "archive" || command.type === "fork") return command.sessionId === undefined;
|
|
6382
|
+
if (command.type === "goal") return command.action !== "set";
|
|
6383
|
+
return false;
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6386
|
+
function unboundThreadTaskMessage(): string {
|
|
6387
|
+
return "当前话题尚未绑定任务。发送普通消息后,Agent Bot 会从可识别的原始轮次创建分支;没有原始轮次时会创建全新任务。也可以使用 /new 创建全新任务,或使用 /sessions 绑定现有任务。";
|
|
6388
|
+
}
|
|
6389
|
+
|
|
6216
6390
|
function parseSessionPage(value: unknown): number {
|
|
6217
6391
|
const parsed = typeof value === "string" || typeof value === "number" ? Number(value) : Number.NaN;
|
|
6218
6392
|
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
@@ -6554,14 +6728,13 @@ const GROUP_PROJECT_DIRECTORY_MAX_LENGTH = 15;
|
|
|
6554
6728
|
|
|
6555
6729
|
function formatGroupProjectDirectory(projectCwd: string): string {
|
|
6556
6730
|
const value = abbreviateHomeDirectory(projectCwd);
|
|
6557
|
-
if (Array.from(value).length <= GROUP_PROJECT_DIRECTORY_MAX_LENGTH) return `[${value}]`;
|
|
6558
|
-
|
|
6559
6731
|
const levels = value
|
|
6560
6732
|
.replace(/[\\/]+$/, "")
|
|
6561
6733
|
.split(/[\\/]+/)
|
|
6562
6734
|
.filter(Boolean)
|
|
6563
6735
|
.slice(-2);
|
|
6564
|
-
|
|
6736
|
+
const separator = value.includes("\\") ? path.win32.sep : path.posix.sep;
|
|
6737
|
+
return `[${fitTrailingPathLevels(levels, GROUP_PROJECT_DIRECTORY_MAX_LENGTH, separator)}]`;
|
|
6565
6738
|
}
|
|
6566
6739
|
|
|
6567
6740
|
function abbreviateHomeDirectory(value: string): string {
|
package/src/state/StateStore.ts
CHANGED
|
@@ -68,6 +68,13 @@ export interface AgentBotTurnMessageRecord extends TurnAnchorRecord {
|
|
|
68
68
|
messageKind: "progress" | "final";
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
export interface GoalCardDeliveryRecord {
|
|
72
|
+
localSessionId: string;
|
|
73
|
+
contextKey: string;
|
|
74
|
+
messageId: string;
|
|
75
|
+
updatedAt: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
71
78
|
export interface TurnRuntimeOriginRecord {
|
|
72
79
|
turnId: string;
|
|
73
80
|
localSessionId: string;
|
|
@@ -242,6 +249,46 @@ export class StateStore {
|
|
|
242
249
|
`);
|
|
243
250
|
}
|
|
244
251
|
|
|
252
|
+
saveGoalCardDelivery(localSessionId: string, contextKey: string, messageId: string): void {
|
|
253
|
+
const updatedAt = new Date().toISOString();
|
|
254
|
+
this.db.prepare(`
|
|
255
|
+
INSERT INTO goal_card_deliveries (local_session_id, context_key, message_id, updated_at)
|
|
256
|
+
VALUES (?, ?, ?, ?)
|
|
257
|
+
ON CONFLICT(local_session_id, context_key) DO UPDATE SET
|
|
258
|
+
message_id = excluded.message_id,
|
|
259
|
+
updated_at = excluded.updated_at
|
|
260
|
+
`).run(localSessionId, contextKey, messageId, updatedAt);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
getGoalCardDelivery(localSessionId: string, contextKey: string): GoalCardDeliveryRecord | undefined {
|
|
264
|
+
const row = this.db.prepare(`
|
|
265
|
+
SELECT local_session_id, context_key, message_id, updated_at
|
|
266
|
+
FROM goal_card_deliveries
|
|
267
|
+
WHERE local_session_id = ? AND context_key = ?
|
|
268
|
+
`).get(localSessionId, contextKey) as {
|
|
269
|
+
local_session_id: string;
|
|
270
|
+
context_key: string;
|
|
271
|
+
message_id: string;
|
|
272
|
+
updated_at: string;
|
|
273
|
+
} | undefined;
|
|
274
|
+
return row ? goalCardDeliveryFromRow(row) : undefined;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
listGoalCardDeliveries(localSessionId: string): GoalCardDeliveryRecord[] {
|
|
278
|
+
const rows = this.db.prepare(`
|
|
279
|
+
SELECT local_session_id, context_key, message_id, updated_at
|
|
280
|
+
FROM goal_card_deliveries
|
|
281
|
+
WHERE local_session_id = ?
|
|
282
|
+
ORDER BY updated_at ASC
|
|
283
|
+
`).all(localSessionId) as Array<{
|
|
284
|
+
local_session_id: string;
|
|
285
|
+
context_key: string;
|
|
286
|
+
message_id: string;
|
|
287
|
+
updated_at: string;
|
|
288
|
+
}>;
|
|
289
|
+
return rows.map(goalCardDeliveryFromRow);
|
|
290
|
+
}
|
|
291
|
+
|
|
245
292
|
close(): void {
|
|
246
293
|
this.db.close();
|
|
247
294
|
}
|
|
@@ -1058,6 +1105,91 @@ export class StateStore {
|
|
|
1058
1105
|
}));
|
|
1059
1106
|
}
|
|
1060
1107
|
|
|
1108
|
+
listTaskTurnGraph(localSessionId: string): CompletedTurnSnapshotRecord[] {
|
|
1109
|
+
this.backfillTurnParents(localSessionId);
|
|
1110
|
+
const records = new Map(
|
|
1111
|
+
this.listCompletedTurnGraphRows(localSessionId).map((record) => [record.turnId, record]),
|
|
1112
|
+
);
|
|
1113
|
+
let ancestorTurnId = this.findForkSourceTurnId(localSessionId)
|
|
1114
|
+
?? this.findCrossSessionParentTurnId(localSessionId);
|
|
1115
|
+
const visited = new Set<string>();
|
|
1116
|
+
|
|
1117
|
+
while (ancestorTurnId && !visited.has(ancestorTurnId)) {
|
|
1118
|
+
visited.add(ancestorTurnId);
|
|
1119
|
+
const ancestor = this.getCompletedTurnGraphRecord(ancestorTurnId);
|
|
1120
|
+
if (!ancestor) break;
|
|
1121
|
+
records.set(ancestor.turnId, ancestor);
|
|
1122
|
+
ancestorTurnId = ancestor.parentTurnId;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
return [...records.values()].sort(compareCompletedTurnSnapshots);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
private listCompletedTurnGraphRows(localSessionId: string): CompletedTurnSnapshotRecord[] {
|
|
1129
|
+
const rows = this.db.prepare(`
|
|
1130
|
+
SELECT ts.turn_id, tpl.parent_turn_id, ts.snapshot_json, ts.updated_at
|
|
1131
|
+
FROM turn_snapshots ts
|
|
1132
|
+
LEFT JOIN turn_parent_links tpl ON tpl.turn_id = ts.turn_id
|
|
1133
|
+
WHERE ts.local_session_id = ?
|
|
1134
|
+
AND json_extract(ts.snapshot_json, '$.status') = 'completed'
|
|
1135
|
+
`).all(localSessionId) as Array<{
|
|
1136
|
+
turn_id: string;
|
|
1137
|
+
parent_turn_id: string | null;
|
|
1138
|
+
snapshot_json: string;
|
|
1139
|
+
updated_at: string;
|
|
1140
|
+
}>;
|
|
1141
|
+
return rows.map(mapCompletedTurnSnapshot);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
private getCompletedTurnGraphRecord(turnId: string): CompletedTurnSnapshotRecord | undefined {
|
|
1145
|
+
const owner = this.db.prepare(`
|
|
1146
|
+
SELECT local_session_id
|
|
1147
|
+
FROM turn_snapshots
|
|
1148
|
+
WHERE turn_id = ?
|
|
1149
|
+
AND json_extract(snapshot_json, '$.status') = 'completed'
|
|
1150
|
+
`).get(turnId) as { local_session_id: string } | undefined;
|
|
1151
|
+
if (!owner) return undefined;
|
|
1152
|
+
this.backfillTurnParents(owner.local_session_id);
|
|
1153
|
+
const row = this.db.prepare(`
|
|
1154
|
+
SELECT ts.turn_id, tpl.parent_turn_id, ts.snapshot_json, ts.updated_at
|
|
1155
|
+
FROM turn_snapshots ts
|
|
1156
|
+
LEFT JOIN turn_parent_links tpl ON tpl.turn_id = ts.turn_id
|
|
1157
|
+
WHERE ts.turn_id = ?
|
|
1158
|
+
AND json_extract(ts.snapshot_json, '$.status') = 'completed'
|
|
1159
|
+
`).get(turnId) as {
|
|
1160
|
+
turn_id: string;
|
|
1161
|
+
parent_turn_id: string | null;
|
|
1162
|
+
snapshot_json: string;
|
|
1163
|
+
updated_at: string;
|
|
1164
|
+
} | undefined;
|
|
1165
|
+
return row ? mapCompletedTurnSnapshot(row) : undefined;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
private findForkSourceTurnId(localSessionId: string): string | undefined {
|
|
1169
|
+
const row = this.db.prepare(`
|
|
1170
|
+
SELECT json_extract(payload_json, '$.sourceTurnId') AS source_turn_id
|
|
1171
|
+
FROM audit_events
|
|
1172
|
+
WHERE event_type IN ('session_forked', 'thread_forked')
|
|
1173
|
+
AND json_extract(payload_json, '$.forkedLocalSessionId') = ?
|
|
1174
|
+
ORDER BY id DESC
|
|
1175
|
+
LIMIT 1
|
|
1176
|
+
`).get(localSessionId) as { source_turn_id: string | null } | undefined;
|
|
1177
|
+
return row?.source_turn_id ?? undefined;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
private findCrossSessionParentTurnId(localSessionId: string): string | undefined {
|
|
1181
|
+
const row = this.db.prepare(`
|
|
1182
|
+
SELECT links.parent_turn_id
|
|
1183
|
+
FROM turn_parent_links links
|
|
1184
|
+
JOIN turn_snapshots parent ON parent.turn_id = links.parent_turn_id
|
|
1185
|
+
WHERE links.local_session_id = ?
|
|
1186
|
+
AND parent.local_session_id <> ?
|
|
1187
|
+
ORDER BY links.created_at ASC
|
|
1188
|
+
LIMIT 1
|
|
1189
|
+
`).get(localSessionId, localSessionId) as { parent_turn_id: string } | undefined;
|
|
1190
|
+
return row?.parent_turn_id;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1061
1193
|
private backfillTurnParents(localSessionId: string): void {
|
|
1062
1194
|
const turns = this.db.prepare(`
|
|
1063
1195
|
SELECT
|
|
@@ -1873,6 +2005,20 @@ function mapMessageReaction(row: MessageReactionRow): MessageReactionRecord {
|
|
|
1873
2005
|
};
|
|
1874
2006
|
}
|
|
1875
2007
|
|
|
2008
|
+
function goalCardDeliveryFromRow(row: {
|
|
2009
|
+
local_session_id: string;
|
|
2010
|
+
context_key: string;
|
|
2011
|
+
message_id: string;
|
|
2012
|
+
updated_at: string;
|
|
2013
|
+
}): GoalCardDeliveryRecord {
|
|
2014
|
+
return {
|
|
2015
|
+
localSessionId: row.local_session_id,
|
|
2016
|
+
contextKey: row.context_key,
|
|
2017
|
+
messageId: row.message_id,
|
|
2018
|
+
updatedAt: row.updated_at,
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1876
2022
|
function mapQueuedPrompt(row: QueuedPromptRow): QueuedPromptRecord {
|
|
1877
2023
|
const parsedPaths = JSON.parse(row.local_image_paths_json) as unknown;
|
|
1878
2024
|
const localImagePaths = Array.isArray(parsedPaths)
|
|
@@ -1915,6 +2061,40 @@ function mapTurnAttempt(row: TurnAttemptRow): TurnAttemptRecord {
|
|
|
1915
2061
|
};
|
|
1916
2062
|
}
|
|
1917
2063
|
|
|
2064
|
+
function mapCompletedTurnSnapshot(row: {
|
|
2065
|
+
turn_id: string;
|
|
2066
|
+
parent_turn_id: string | null;
|
|
2067
|
+
snapshot_json: string;
|
|
2068
|
+
updated_at: string;
|
|
2069
|
+
}): CompletedTurnSnapshotRecord {
|
|
2070
|
+
return {
|
|
2071
|
+
turnId: row.turn_id,
|
|
2072
|
+
parentTurnId: row.parent_turn_id ?? undefined,
|
|
2073
|
+
snapshot: JSON.parse(row.snapshot_json),
|
|
2074
|
+
updatedAt: row.updated_at,
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
function compareCompletedTurnSnapshots(
|
|
2079
|
+
left: CompletedTurnSnapshotRecord,
|
|
2080
|
+
right: CompletedTurnSnapshotRecord,
|
|
2081
|
+
): number {
|
|
2082
|
+
const timeDifference = completedTurnTimestamp(right) - completedTurnTimestamp(left);
|
|
2083
|
+
return timeDifference || right.updatedAt.localeCompare(left.updatedAt);
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
function completedTurnTimestamp(record: CompletedTurnSnapshotRecord): number {
|
|
2087
|
+
const snapshot = record.snapshot && typeof record.snapshot === "object"
|
|
2088
|
+
? record.snapshot as { completedAt?: unknown; startedAt?: unknown }
|
|
2089
|
+
: undefined;
|
|
2090
|
+
const timestamp = typeof snapshot?.completedAt === "number"
|
|
2091
|
+
? snapshot.completedAt
|
|
2092
|
+
: typeof snapshot?.startedAt === "number"
|
|
2093
|
+
? snapshot.startedAt
|
|
2094
|
+
: Date.parse(record.updatedAt);
|
|
2095
|
+
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
1918
2098
|
function isIncompleteTurnAttemptStatus(status: TurnAttemptStatus): boolean {
|
|
1919
2099
|
return status === "accepted" || status === "running" || status === "recovering";
|
|
1920
2100
|
}
|
package/src/state/migrations.ts
CHANGED
|
@@ -173,4 +173,15 @@ export const migrations = [
|
|
|
173
173
|
CREATE INDEX IF NOT EXISTS idx_turn_attempts_turn
|
|
174
174
|
ON turn_attempts(turn_id);
|
|
175
175
|
`,
|
|
176
|
+
`
|
|
177
|
+
CREATE TABLE IF NOT EXISTS goal_card_deliveries (
|
|
178
|
+
local_session_id TEXT NOT NULL,
|
|
179
|
+
context_key TEXT NOT NULL,
|
|
180
|
+
message_id TEXT NOT NULL,
|
|
181
|
+
updated_at TEXT NOT NULL,
|
|
182
|
+
PRIMARY KEY (local_session_id, context_key)
|
|
183
|
+
);
|
|
184
|
+
CREATE INDEX IF NOT EXISTS idx_goal_card_deliveries_session
|
|
185
|
+
ON goal_card_deliveries(local_session_id);
|
|
186
|
+
`,
|
|
176
187
|
] as const;
|