@keyou007/agent-bot 0.1.17 → 0.1.18-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 +11 -0
- package/dist/feishu/FeishuConnector.js +7 -1
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/local-files/LocalFileViewerServer.js +107 -5
- package/dist/local-files/LocalFileViewerServer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +90 -15
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/shell/ShellCommandJobManager.js +33 -0
- package/dist/shell/ShellCommandJobManager.js.map +1 -1
- package/npm-shrinkwrap.json +98 -2
- package/package.json +2 -1
- package/src/feishu/FeishuConnector.ts +5 -2
- package/src/local-files/LocalFileViewerServer.ts +112 -5
- package/src/proxy/ProxySessionController.ts +96 -14
- package/src/shell/ShellCommandJobManager.ts +30 -0
|
@@ -1757,7 +1757,7 @@ export class ProxySessionController {
|
|
|
1757
1757
|
}
|
|
1758
1758
|
switch (command.type) {
|
|
1759
1759
|
case "shell":
|
|
1760
|
-
await this.runShellCommand(contextKey, command.command, messageId);
|
|
1760
|
+
await this.runShellCommand(contextKey, command.command, messageId, incomingMessage);
|
|
1761
1761
|
return;
|
|
1762
1762
|
case "dir":
|
|
1763
1763
|
await this.openDirectoryBrowser(contextKey, command.directory);
|
|
@@ -2817,10 +2817,35 @@ export class ProxySessionController {
|
|
|
2817
2817
|
if (!runtime.readRemoteSession && !source) {
|
|
2818
2818
|
throw new Error("当前 App Server Agent 不支持读取指定任务。");
|
|
2819
2819
|
}
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
: undefined
|
|
2823
|
-
|
|
2820
|
+
const persistedCompletedTurns = source ? this.localForkCompletedTurns(source) : [];
|
|
2821
|
+
const sourceLastSnapshot = turnViewSnapshot(
|
|
2822
|
+
source?.lastTurnId ? this.store.getTurnSnapshot(source.lastTurnId) : undefined,
|
|
2823
|
+
);
|
|
2824
|
+
const localCompletedTurns = persistedCompletedTurns.length > 0
|
|
2825
|
+
? persistedCompletedTurns
|
|
2826
|
+
: source?.lastTurnId
|
|
2827
|
+
&& (source.lastTurnStatus === "completed" || sourceLastSnapshot?.status === "completed")
|
|
2828
|
+
? [{
|
|
2829
|
+
id: source.lastTurnId,
|
|
2830
|
+
prompt: latestUserPromptFromTurnView(sourceLastSnapshot) ?? source.title,
|
|
2831
|
+
startedAt: sourceLastSnapshot?.startedAt ?? parseIsoTimestamp(source.updatedAt),
|
|
2832
|
+
completedAt: sourceLastSnapshot?.completedAt
|
|
2833
|
+
?? sourceLastSnapshot?.startedAt
|
|
2834
|
+
?? parseIsoTimestamp(source.updatedAt),
|
|
2835
|
+
}]
|
|
2836
|
+
: [];
|
|
2837
|
+
if (!remote && runtime.readRemoteSession && (!source || persistedCompletedTurns.length === 0)) {
|
|
2838
|
+
try {
|
|
2839
|
+
remote = await runtime.readRemoteSession(remoteSessionId);
|
|
2840
|
+
} catch (error) {
|
|
2841
|
+
if (localCompletedTurns.length === 0) throw error;
|
|
2842
|
+
this.logger.warn(
|
|
2843
|
+
{ error, contextKey, remoteSessionId, sourceTurnId: localCompletedTurns.at(-1)?.id },
|
|
2844
|
+
"Failed to read the complete source task before Fork; using the locally persisted completed Turn.",
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
const latestTurnId = remote?.lastTurnId ?? source?.lastTurnId ?? localCompletedTurns.at(-1)?.id;
|
|
2824
2849
|
const latestSnapshot = turnViewSnapshot(latestTurnId ? this.store.getTurnSnapshot(latestTurnId) : undefined);
|
|
2825
2850
|
const isRunning = remote
|
|
2826
2851
|
? isRemoteSessionActive(remote)
|
|
@@ -2829,7 +2854,9 @@ export class ProxySessionController {
|
|
|
2829
2854
|
|| source?.lastTurnStatus === "running"
|
|
2830
2855
|
|| isTurnStillRunning(latestSnapshot?.status),
|
|
2831
2856
|
);
|
|
2832
|
-
const lastTurnId =
|
|
2857
|
+
const lastTurnId = remote?.lastCompletedTurnId
|
|
2858
|
+
?? (remote?.lastTurnStatus === "completed" ? remote.lastTurnId : undefined)
|
|
2859
|
+
?? localCompletedTurns.at(-1)?.id;
|
|
2833
2860
|
if (!lastTurnId) {
|
|
2834
2861
|
if (isRunning) {
|
|
2835
2862
|
throw new Error(`${sourceLabel}正在执行,且还没有已完成轮次可供 fork。请等待当前轮次完成后重试。`);
|
|
@@ -2837,7 +2864,9 @@ export class ProxySessionController {
|
|
|
2837
2864
|
throw new Error(`${sourceLabel}还没有可供 fork 的轮次。请先完成至少一轮对话。`);
|
|
2838
2865
|
}
|
|
2839
2866
|
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(lastTurnId));
|
|
2840
|
-
const forkedFromHistoricalTurn =
|
|
2867
|
+
const forkedFromHistoricalTurn = lastTurnId !== latestTurnId;
|
|
2868
|
+
const completedTurn = (remote?.completedTurns ?? localCompletedTurns)
|
|
2869
|
+
.find((turn) => turn.id === lastTurnId);
|
|
2841
2870
|
|
|
2842
2871
|
const cwd = remote?.cwd || source?.cwd;
|
|
2843
2872
|
if (!cwd) throw new Error("指定的 App Server 任务没有可用的工作目录,暂时不能 fork。");
|
|
@@ -2849,6 +2878,7 @@ export class ProxySessionController {
|
|
|
2849
2878
|
const permissionMode = source?.permissionMode ?? "auto";
|
|
2850
2879
|
const sourceTurnPrompt = truncateText(
|
|
2851
2880
|
latestUserPromptFromTurnView(snapshot)
|
|
2881
|
+
?? completedTurn?.prompt
|
|
2852
2882
|
?? (lastTurnId === latestTurnId ? remote?.lastUserPrompt : undefined)
|
|
2853
2883
|
?? remote?.preview
|
|
2854
2884
|
?? source?.title
|
|
@@ -2858,6 +2888,8 @@ export class ProxySessionController {
|
|
|
2858
2888
|
);
|
|
2859
2889
|
const sourceTurnCompletedAt = snapshot?.completedAt
|
|
2860
2890
|
?? snapshot?.startedAt
|
|
2891
|
+
?? completedTurn?.completedAt
|
|
2892
|
+
?? completedTurn?.startedAt
|
|
2861
2893
|
?? (lastTurnId === latestTurnId
|
|
2862
2894
|
? latestRemoteTimestamp(remote?.recencyAt, remote?.updatedAt)
|
|
2863
2895
|
: undefined)
|
|
@@ -2876,20 +2908,33 @@ export class ProxySessionController {
|
|
|
2876
2908
|
model,
|
|
2877
2909
|
reasoningEffort,
|
|
2878
2910
|
permissionMode,
|
|
2879
|
-
lastTurnStatus:
|
|
2880
|
-
? "completed"
|
|
2881
|
-
: mapRemoteTurnStatus(remote?.lastTurnStatus)
|
|
2882
|
-
?? forkedTurnStatus(snapshot?.status)
|
|
2883
|
-
?? source?.lastTurnStatus,
|
|
2911
|
+
lastTurnStatus: "completed",
|
|
2884
2912
|
sourceTurnPrompt,
|
|
2885
2913
|
sourceTurnStartedAt: snapshot?.startedAt ?? sourceTurnCompletedAt,
|
|
2886
2914
|
sourceTurnCompletedAt,
|
|
2887
|
-
sourceCompletedTurns: remote?.completedTurns
|
|
2915
|
+
sourceCompletedTurns: remote?.completedTurns
|
|
2916
|
+
?? (persistedCompletedTurns.length > 0 ? [] : localCompletedTurns),
|
|
2888
2917
|
sourceWasRunning: isRunning,
|
|
2889
2918
|
forkedFromHistoricalTurn,
|
|
2890
2919
|
};
|
|
2891
2920
|
}
|
|
2892
2921
|
|
|
2922
|
+
private localForkCompletedTurns(source: SessionRecord): RemoteCompletedTurnSummary[] {
|
|
2923
|
+
return this.store.listTaskTurnGraph(source.localSessionId)
|
|
2924
|
+
.slice()
|
|
2925
|
+
.reverse()
|
|
2926
|
+
.map((record) => {
|
|
2927
|
+
const snapshot = turnViewSnapshot(record.snapshot);
|
|
2928
|
+
const startedAt = snapshot?.startedAt ?? parseIsoTimestamp(record.updatedAt);
|
|
2929
|
+
return {
|
|
2930
|
+
id: record.turnId,
|
|
2931
|
+
prompt: latestUserPromptFromTurnView(snapshot),
|
|
2932
|
+
startedAt,
|
|
2933
|
+
completedAt: snapshot?.completedAt ?? startedAt,
|
|
2934
|
+
};
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2893
2938
|
private async prepareForkGroupSession(
|
|
2894
2939
|
contextKey: string,
|
|
2895
2940
|
requestedTitle: string | undefined,
|
|
@@ -4963,8 +5008,9 @@ export class ProxySessionController {
|
|
|
4963
5008
|
contextKey: string,
|
|
4964
5009
|
command: string,
|
|
4965
5010
|
sourceMessageId?: string,
|
|
5011
|
+
incomingMessage?: IncomingMessage,
|
|
4966
5012
|
): Promise<void> {
|
|
4967
|
-
const cwd = this.
|
|
5013
|
+
const cwd = await this.shellCommandCwd(contextKey, incomingMessage);
|
|
4968
5014
|
const created = await this.shellCommandJobs.createJob({ contextKey, sourceMessageId, command, cwd });
|
|
4969
5015
|
let cardMessageId: string | undefined;
|
|
4970
5016
|
const initialState: ShellCommandCardView = {
|
|
@@ -5010,6 +5056,42 @@ export class ProxySessionController {
|
|
|
5010
5056
|
}
|
|
5011
5057
|
}
|
|
5012
5058
|
|
|
5059
|
+
private async shellCommandCwd(contextKey: string, incomingMessage?: IncomingMessage): Promise<string> {
|
|
5060
|
+
const current = this.currentSession(contextKey);
|
|
5061
|
+
if (current) return current.cwd;
|
|
5062
|
+
|
|
5063
|
+
if (incomingMessage?.threadContext) {
|
|
5064
|
+
const anchorMessageIds = threadForkAnchorMessageIds(incomingMessage);
|
|
5065
|
+
const anchor = anchorMessageIds
|
|
5066
|
+
.map((messageId) => this.store.findTurnAnchorByMessageId(messageId))
|
|
5067
|
+
.find((candidate) => candidate !== undefined);
|
|
5068
|
+
const source = anchor?.contextKey
|
|
5069
|
+
? this.store.getSessionForContext(anchor.localSessionId, anchor.contextKey)
|
|
5070
|
+
: anchor
|
|
5071
|
+
? this.store.getSession(anchor.localSessionId)
|
|
5072
|
+
: undefined;
|
|
5073
|
+
const sourceContextKey = anchor?.contextKey ?? source?.contextKey;
|
|
5074
|
+
if (source && sourceContextKey && baseChatContextKey(sourceContextKey) === baseChatContextKey(contextKey)) {
|
|
5075
|
+
return source.cwd;
|
|
5076
|
+
}
|
|
5077
|
+
|
|
5078
|
+
for (const messageId of anchorMessageIds) {
|
|
5079
|
+
const commandJob = await this.shellCommandJobs.findJobByCardMessageId(messageId);
|
|
5080
|
+
if (commandJob && baseChatContextKey(commandJob.contextKey) === baseChatContextKey(contextKey)) {
|
|
5081
|
+
return commandJob.cwd;
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
|
|
5085
|
+
const baseContextKey = baseChatContextKey(contextKey);
|
|
5086
|
+
const baseSession = this.currentSession(baseContextKey);
|
|
5087
|
+
if (baseSession) return baseSession.cwd;
|
|
5088
|
+
const boundProjectCwd = this.store.getUserContext(baseContextKey)?.boundProjectCwd;
|
|
5089
|
+
if (boundProjectCwd) return boundProjectCwd;
|
|
5090
|
+
}
|
|
5091
|
+
|
|
5092
|
+
return this.config.defaults.cwd;
|
|
5093
|
+
}
|
|
5094
|
+
|
|
5013
5095
|
private monitorShellCommandJob(
|
|
5014
5096
|
job: ShellCommandJobSnapshot,
|
|
5015
5097
|
seededState?: ShellCommandCardView,
|
|
@@ -52,6 +52,7 @@ export interface ShellCommandJobSnapshot extends ShellCommandJobSpec, Omit<Shell
|
|
|
52
52
|
export interface ShellCommandJobManagerLike {
|
|
53
53
|
createJob(input: Pick<ShellCommandJobSpec, "contextKey" | "sourceMessageId" | "command" | "cwd">): Promise<ShellCommandJobSnapshot>;
|
|
54
54
|
bindCard(jobId: string, cardMessageId: string): Promise<ShellCommandJobSnapshot>;
|
|
55
|
+
findJobByCardMessageId(cardMessageId: string): Promise<ShellCommandJobSnapshot | undefined>;
|
|
55
56
|
startJob(jobId: string): Promise<void>;
|
|
56
57
|
failJob(jobId: string, error: string): Promise<void>;
|
|
57
58
|
readJob(jobId: string): Promise<ShellCommandJobSnapshot>;
|
|
@@ -68,6 +69,7 @@ export interface ShellCommandJobManagerOptions {
|
|
|
68
69
|
export class ShellCommandJobManager implements ShellCommandJobManagerLike {
|
|
69
70
|
private readonly runnerEntry: string;
|
|
70
71
|
private readonly spawnRunner: (entry: string, jobDirectory: string) => Promise<void>;
|
|
72
|
+
private readonly jobIdByCardMessageId = new Map<string, string>();
|
|
71
73
|
|
|
72
74
|
constructor(
|
|
73
75
|
private readonly jobsRoot: string,
|
|
@@ -102,9 +104,37 @@ export class ShellCommandJobManager implements ShellCommandJobManagerLike {
|
|
|
102
104
|
const jobDirectory = this.jobDirectory(jobId);
|
|
103
105
|
const spec = await readJson<ShellCommandJobSpec>(path.join(jobDirectory, JOB_SPEC_FILE));
|
|
104
106
|
await writeJsonAtomic(path.join(jobDirectory, JOB_SPEC_FILE), { ...spec, cardMessageId });
|
|
107
|
+
this.jobIdByCardMessageId.set(cardMessageId, jobId);
|
|
105
108
|
return this.readJob(jobId);
|
|
106
109
|
}
|
|
107
110
|
|
|
111
|
+
async findJobByCardMessageId(cardMessageId: string): Promise<ShellCommandJobSnapshot | undefined> {
|
|
112
|
+
const cachedJobId = this.jobIdByCardMessageId.get(cardMessageId);
|
|
113
|
+
if (cachedJobId) {
|
|
114
|
+
try {
|
|
115
|
+
return await this.readJob(cachedJobId);
|
|
116
|
+
} catch {
|
|
117
|
+
this.jobIdByCardMessageId.delete(cardMessageId);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const entries = await fs.readdir(this.jobsRoot, { withFileTypes: true }).catch((error: NodeJS.ErrnoException) => {
|
|
122
|
+
if (error.code === "ENOENT") return [];
|
|
123
|
+
throw error;
|
|
124
|
+
});
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
if (!entry.isDirectory()) continue;
|
|
127
|
+
try {
|
|
128
|
+
const spec = await readJson<ShellCommandJobSpec>(path.join(this.jobsRoot, entry.name, JOB_SPEC_FILE));
|
|
129
|
+
if (spec.cardMessageId) this.jobIdByCardMessageId.set(spec.cardMessageId, spec.id);
|
|
130
|
+
if (spec.cardMessageId === cardMessageId) return this.readJob(spec.id);
|
|
131
|
+
} catch {
|
|
132
|
+
// Ignore partially written or unrelated job directories while rebuilding the card index.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
108
138
|
async startJob(jobId: string): Promise<void> {
|
|
109
139
|
const jobDirectory = this.jobDirectory(jobId);
|
|
110
140
|
await this.spawnRunner(this.runnerEntry, jobDirectory);
|