@keyou007/agent-bot 0.1.16-alpha.4 → 0.1.17-alpha.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/CHANGELOG.md +12 -0
- package/README.md +4 -2
- package/README.zh.md +4 -2
- package/config.example.yaml +8 -0
- package/dist/cli/help.js +4 -4
- package/dist/cli/taskTarget.js +7 -0
- package/dist/cli/taskTarget.js.map +1 -1
- package/dist/cli.js +12 -12
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexRuntime.js +40 -12
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/config/AgentExecutionDefaultsStore.js +71 -0
- package/dist/config/AgentExecutionDefaultsStore.js.map +1 -0
- package/dist/config/loadConfig.js +8 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +7 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/feishu/CardRenderer.js +29 -27
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +255 -30
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +62 -9
- package/dist/state/StateStore.js.map +1 -1
- package/dist/supervision/restartNotificationTarget.js +9 -0
- package/dist/supervision/restartNotificationTarget.js.map +1 -0
- package/docs/technical-reference.md +7 -3
- package/docs/technical-reference.zh.md +7 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +3 -1
- package/src/cli/help.ts +4 -4
- package/src/cli/taskTarget.ts +11 -0
- package/src/cli.ts +16 -20
- package/src/codex/CodexRuntime.ts +38 -10
- package/src/config/AgentExecutionDefaultsStore.ts +71 -0
- package/src/config/loadConfig.ts +8 -0
- package/src/config/schema.ts +13 -1
- package/src/feishu/CardRenderer.ts +30 -22
- package/src/index.ts +17 -2
- package/src/proxy/ProxySessionController.ts +330 -40
- package/src/runtime/types.ts +8 -0
- package/src/state/StateStore.ts +100 -9
- package/src/supervision/restartNotificationTarget.ts +12 -0
|
@@ -5,7 +5,11 @@ import path from "node:path";
|
|
|
5
5
|
import type { Logger } from "pino";
|
|
6
6
|
import { createProjectlessWorkspace, detectProjectlessWorkspace } from "../codex/ProjectlessWorkspace.js";
|
|
7
7
|
import { resolveUserPath } from "../config/paths.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_GROUP_NAME_FORMAT,
|
|
10
|
+
type AgentExecutionDefaults,
|
|
11
|
+
type AppConfig,
|
|
12
|
+
} from "../config/schema.js";
|
|
9
13
|
import { CommandRouter } from "../commands/CommandRouter.js";
|
|
10
14
|
import type { Command } from "../commands/commandTypes.js";
|
|
11
15
|
import { baseChatContextKey, isThreadContextKey } from "../feishu/contextKey.js";
|
|
@@ -45,6 +49,7 @@ import type {
|
|
|
45
49
|
AgentRuntime,
|
|
46
50
|
ApprovalDecision,
|
|
47
51
|
PermissionMode,
|
|
52
|
+
RemoteCompletedTurnSummary,
|
|
48
53
|
RemoteSessionActivity,
|
|
49
54
|
RemoteSessionSummary,
|
|
50
55
|
RuntimeGoal,
|
|
@@ -323,12 +328,7 @@ interface ExecutionSettingsCardOptions extends ModelCardOptions {
|
|
|
323
328
|
notice?: string;
|
|
324
329
|
}
|
|
325
330
|
|
|
326
|
-
|
|
327
|
-
modelProvider?: string;
|
|
328
|
-
model?: string;
|
|
329
|
-
reasoningEffort?: string;
|
|
330
|
-
permissionMode?: PermissionMode;
|
|
331
|
-
}
|
|
331
|
+
type SessionExecutionSettings = AgentExecutionDefaults;
|
|
332
332
|
|
|
333
333
|
interface ProjectSessionReference {
|
|
334
334
|
source?: SessionRecord;
|
|
@@ -352,6 +352,10 @@ interface ForkSessionPlan {
|
|
|
352
352
|
reasoningEffort?: string;
|
|
353
353
|
permissionMode: PermissionMode;
|
|
354
354
|
lastTurnStatus?: SessionRecord["lastTurnStatus"];
|
|
355
|
+
sourceTurnPrompt?: string;
|
|
356
|
+
sourceTurnStartedAt?: number;
|
|
357
|
+
sourceTurnCompletedAt?: number;
|
|
358
|
+
sourceCompletedTurns?: RemoteCompletedTurnSummary[];
|
|
355
359
|
sourceWasRunning: boolean;
|
|
356
360
|
forkedFromHistoricalTurn: boolean;
|
|
357
361
|
}
|
|
@@ -395,6 +399,10 @@ export interface ProxyLifecycle {
|
|
|
395
399
|
restart(contextKey: string, force: boolean, replyTarget?: MessageReplyTarget): Promise<void>;
|
|
396
400
|
cancelSafeRestart?(scheduleId: number): Promise<boolean>;
|
|
397
401
|
rememberFeishuUserOpenId?(userOpenId: string): Promise<void> | void;
|
|
402
|
+
persistAgentExecutionDefaults?(
|
|
403
|
+
agentName: string,
|
|
404
|
+
defaults: AgentExecutionDefaults,
|
|
405
|
+
): Promise<void> | void;
|
|
398
406
|
}
|
|
399
407
|
|
|
400
408
|
export type ShellCommandExecutor = (
|
|
@@ -419,6 +427,7 @@ export class ProxySessionController {
|
|
|
419
427
|
private readonly queuedPromptCards = new Map<string, Map<string, string>>();
|
|
420
428
|
private readonly queuedPromptCardWrites = new Map<string, Promise<void>>();
|
|
421
429
|
private readonly sessionResets = new Map<string, Promise<void>>();
|
|
430
|
+
private readonly resetHistoryOperations = new Map<string, string>();
|
|
422
431
|
private readonly lastSessionListings = new Map<string, string[]>();
|
|
423
432
|
private readonly threadInitializations = new Map<string, Promise<void>>();
|
|
424
433
|
private readonly llmRetryingSessions = new Set<string>();
|
|
@@ -824,17 +833,17 @@ export class ProxySessionController {
|
|
|
824
833
|
);
|
|
825
834
|
} else if (kind === "turn_reset") {
|
|
826
835
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
827
|
-
|
|
828
|
-
contextKey,
|
|
829
|
-
sessionId,
|
|
830
|
-
String(scopedAction.value.turnId ?? ""),
|
|
831
|
-
);
|
|
836
|
+
const turnId = String(scopedAction.value.turnId ?? "");
|
|
832
837
|
if (scopedAction.value.cardView === "reset_history") {
|
|
833
|
-
await this.
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
+
await this.resetFromHistoryCard(
|
|
839
|
+
contextKey,
|
|
840
|
+
sessionId,
|
|
841
|
+
turnId,
|
|
842
|
+
requiredCardMessageId(scopedAction.messageId),
|
|
843
|
+
resetHistoryPageValue(scopedAction.value.page),
|
|
844
|
+
);
|
|
845
|
+
} else {
|
|
846
|
+
await this.resetCurrentSessionToTurn(contextKey, sessionId, turnId);
|
|
838
847
|
}
|
|
839
848
|
} else if (kind === "turn_reset_page") {
|
|
840
849
|
await this.openResetHistory(contextKey, {
|
|
@@ -1086,6 +1095,7 @@ export class ProxySessionController {
|
|
|
1086
1095
|
this.queuedPromptCards.clear();
|
|
1087
1096
|
this.queuedPromptCardWrites.clear();
|
|
1088
1097
|
this.sessionResets.clear();
|
|
1098
|
+
this.resetHistoryOperations.clear();
|
|
1089
1099
|
for (const pending of this.pendingMergedForwards.values()) pending.resolveAttachment(undefined);
|
|
1090
1100
|
this.pendingMergedForwards.clear();
|
|
1091
1101
|
for (const pending of this.pendingResourceForwards.values()) pending.resolveAttachment(undefined);
|
|
@@ -1506,6 +1516,7 @@ export class ProxySessionController {
|
|
|
1506
1516
|
this.store.updateRuntimeSession(record.localSessionId, { permissionMode: nextValue });
|
|
1507
1517
|
}
|
|
1508
1518
|
const session = this.store.getSession(record.localSessionId) ?? record;
|
|
1519
|
+
if (setting) await this.persistAgentExecutionDefaults(session.localSessionId);
|
|
1509
1520
|
const models = await loaded.runtime.listModels();
|
|
1510
1521
|
const currentModel = models.find((candidate) => candidate.id === session.model)
|
|
1511
1522
|
?? models.find((candidate) => candidate.isDefault);
|
|
@@ -2827,6 +2838,21 @@ export class ProxySessionController {
|
|
|
2827
2838
|
const model = remote?.model ?? source?.model;
|
|
2828
2839
|
const reasoningEffort = source?.reasoningEffort;
|
|
2829
2840
|
const permissionMode = source?.permissionMode ?? "auto";
|
|
2841
|
+
const sourceTurnPrompt = truncateText(
|
|
2842
|
+
latestUserPromptFromTurnView(snapshot)
|
|
2843
|
+
?? (lastTurnId === latestTurnId ? remote?.lastUserPrompt : undefined)
|
|
2844
|
+
?? remote?.preview
|
|
2845
|
+
?? source?.title
|
|
2846
|
+
?? remote?.title
|
|
2847
|
+
?? "Fork 来源轮次",
|
|
2848
|
+
1_000,
|
|
2849
|
+
);
|
|
2850
|
+
const sourceTurnCompletedAt = snapshot?.completedAt
|
|
2851
|
+
?? snapshot?.startedAt
|
|
2852
|
+
?? (lastTurnId === latestTurnId
|
|
2853
|
+
? latestRemoteTimestamp(remote?.recencyAt, remote?.updatedAt)
|
|
2854
|
+
: undefined)
|
|
2855
|
+
?? parseIsoTimestamp(source?.updatedAt);
|
|
2830
2856
|
|
|
2831
2857
|
return {
|
|
2832
2858
|
source,
|
|
@@ -2846,6 +2872,10 @@ export class ProxySessionController {
|
|
|
2846
2872
|
: mapRemoteTurnStatus(remote?.lastTurnStatus)
|
|
2847
2873
|
?? forkedTurnStatus(snapshot?.status)
|
|
2848
2874
|
?? source?.lastTurnStatus,
|
|
2875
|
+
sourceTurnPrompt,
|
|
2876
|
+
sourceTurnStartedAt: snapshot?.startedAt ?? sourceTurnCompletedAt,
|
|
2877
|
+
sourceTurnCompletedAt,
|
|
2878
|
+
sourceCompletedTurns: remote?.completedTurns ?? [],
|
|
2849
2879
|
sourceWasRunning: isRunning,
|
|
2850
2880
|
forkedFromHistoricalTurn,
|
|
2851
2881
|
};
|
|
@@ -2918,6 +2948,9 @@ export class ProxySessionController {
|
|
|
2918
2948
|
|| source.status === "running",
|
|
2919
2949
|
);
|
|
2920
2950
|
const forkTitle = normalizeTaskTitle(requestedTitle) ?? this.store.nextForkTitle(source.title);
|
|
2951
|
+
const sourceTurnCompletedAt = snapshot?.completedAt
|
|
2952
|
+
?? snapshot?.startedAt
|
|
2953
|
+
?? parseIsoTimestamp(source.updatedAt);
|
|
2921
2954
|
|
|
2922
2955
|
return {
|
|
2923
2956
|
source,
|
|
@@ -2935,6 +2968,12 @@ export class ProxySessionController {
|
|
|
2935
2968
|
lastTurnStatus: forkedTurnStatus(snapshot?.status)
|
|
2936
2969
|
?? (source.lastTurnId === lastTurnId ? source.lastTurnStatus : undefined)
|
|
2937
2970
|
?? "completed",
|
|
2971
|
+
sourceTurnPrompt: truncateText(
|
|
2972
|
+
latestUserPromptFromTurnView(snapshot) ?? source.title ?? "Fork 来源轮次",
|
|
2973
|
+
1_000,
|
|
2974
|
+
),
|
|
2975
|
+
sourceTurnStartedAt: snapshot?.startedAt ?? sourceTurnCompletedAt,
|
|
2976
|
+
sourceTurnCompletedAt,
|
|
2938
2977
|
sourceWasRunning,
|
|
2939
2978
|
forkedFromHistoricalTurn: sourceWasRunning && source.lastTurnId !== lastTurnId,
|
|
2940
2979
|
};
|
|
@@ -2944,6 +2983,7 @@ export class ProxySessionController {
|
|
|
2944
2983
|
contextKey: string,
|
|
2945
2984
|
plan: ForkSessionPlan,
|
|
2946
2985
|
): Promise<ForkSessionResult> {
|
|
2986
|
+
const sourceCompletedTurns = await this.resolveForkSourceCompletedTurns(plan);
|
|
2947
2987
|
const localSessionId = createId("sess");
|
|
2948
2988
|
const record = this.store.createSession({
|
|
2949
2989
|
localSessionId,
|
|
@@ -2979,7 +3019,6 @@ export class ProxySessionController {
|
|
|
2979
3019
|
lastTurnId: plan.lastTurnId,
|
|
2980
3020
|
lastTurnStatus: plan.lastTurnStatus,
|
|
2981
3021
|
});
|
|
2982
|
-
this.store.setCurrentSession(contextKey, localSessionId);
|
|
2983
3022
|
this.outbound.registerSession(
|
|
2984
3023
|
localSessionId,
|
|
2985
3024
|
contextKey,
|
|
@@ -2987,14 +3026,25 @@ export class ProxySessionController {
|
|
|
2987
3026
|
plan.cwd,
|
|
2988
3027
|
this.agentLabel(plan.agentName),
|
|
2989
3028
|
);
|
|
3029
|
+
const sourceTurnHistoryCount = this.persistForkSourceHistory(
|
|
3030
|
+
contextKey,
|
|
3031
|
+
localSessionId,
|
|
3032
|
+
plan,
|
|
3033
|
+
sourceCompletedTurns,
|
|
3034
|
+
);
|
|
2990
3035
|
this.store.audit(contextKey, "session_forked", {
|
|
2991
3036
|
sourceLocalSessionId: plan.source?.localSessionId,
|
|
2992
3037
|
sourceRemoteSessionId: plan.remoteSessionId,
|
|
2993
3038
|
sourceTurnId: plan.lastTurnId,
|
|
3039
|
+
sourceTurnPrompt: plan.sourceTurnPrompt,
|
|
3040
|
+
sourceTurnStartedAt: plan.sourceTurnStartedAt,
|
|
3041
|
+
sourceTurnCompletedAt: plan.sourceTurnCompletedAt,
|
|
3042
|
+
sourceTurnHistoryCount,
|
|
2994
3043
|
sourceWasRunning: plan.sourceWasRunning,
|
|
2995
3044
|
forkedLocalSessionId: localSessionId,
|
|
2996
3045
|
forkedRemoteSessionId: forked.remoteSessionId,
|
|
2997
3046
|
});
|
|
3047
|
+
this.store.setCurrentSession(contextKey, localSessionId);
|
|
2998
3048
|
return {
|
|
2999
3049
|
record: this.store.getSession(localSessionId) ?? record,
|
|
3000
3050
|
session: forked,
|
|
@@ -3005,6 +3055,146 @@ export class ProxySessionController {
|
|
|
3005
3055
|
}
|
|
3006
3056
|
}
|
|
3007
3057
|
|
|
3058
|
+
private async resolveForkSourceCompletedTurns(
|
|
3059
|
+
plan: ForkSessionPlan,
|
|
3060
|
+
): Promise<RemoteCompletedTurnSummary[]> {
|
|
3061
|
+
let completedTurns = plan.sourceCompletedTurns;
|
|
3062
|
+
if (completedTurns === undefined && plan.runtime.readRemoteSession) {
|
|
3063
|
+
try {
|
|
3064
|
+
completedTurns = (await plan.runtime.readRemoteSession(plan.remoteSessionId)).completedTurns ?? [];
|
|
3065
|
+
} catch (error) {
|
|
3066
|
+
this.logger.warn(
|
|
3067
|
+
{ error, remoteSessionId: plan.remoteSessionId, sourceTurnId: plan.lastTurnId },
|
|
3068
|
+
"Failed to read the source task Turn history before Fork; keeping the Fork anchor fallback.",
|
|
3069
|
+
);
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
if (!completedTurns?.length) return [];
|
|
3073
|
+
const anchorIndex = completedTurns.findIndex((turn) => turn.id === plan.lastTurnId);
|
|
3074
|
+
if (anchorIndex < 0) {
|
|
3075
|
+
this.logger.warn(
|
|
3076
|
+
{ remoteSessionId: plan.remoteSessionId, sourceTurnId: plan.lastTurnId },
|
|
3077
|
+
"The source task Turn history did not include the Fork anchor; keeping the Fork anchor fallback.",
|
|
3078
|
+
);
|
|
3079
|
+
return [];
|
|
3080
|
+
}
|
|
3081
|
+
return completedTurns.slice(0, anchorIndex + 1);
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
private persistForkSourceHistory(
|
|
3085
|
+
contextKey: string,
|
|
3086
|
+
forkedLocalSessionId: string,
|
|
3087
|
+
plan: ForkSessionPlan,
|
|
3088
|
+
completedTurns: RemoteCompletedTurnSummary[],
|
|
3089
|
+
): number {
|
|
3090
|
+
if (completedTurns.length === 0) return 0;
|
|
3091
|
+
const localSessionId = plan.source?.localSessionId ?? forkedLocalSessionId;
|
|
3092
|
+
const historyContextKey = plan.source?.contextKey ?? contextKey;
|
|
3093
|
+
const fallbackStart = plan.sourceTurnStartedAt
|
|
3094
|
+
?? plan.sourceTurnCompletedAt
|
|
3095
|
+
?? Date.now() - completedTurns.length;
|
|
3096
|
+
return this.persistCompletedTurnHistory({
|
|
3097
|
+
localSessionId,
|
|
3098
|
+
contextKey: historyContextKey,
|
|
3099
|
+
agentName: plan.agentName,
|
|
3100
|
+
remoteSessionId: plan.remoteSessionId,
|
|
3101
|
+
completedTurns,
|
|
3102
|
+
fallbackStart,
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
private persistCompletedTurnHistory(input: {
|
|
3107
|
+
localSessionId: string;
|
|
3108
|
+
contextKey: string;
|
|
3109
|
+
agentName: string;
|
|
3110
|
+
remoteSessionId: string;
|
|
3111
|
+
completedTurns: RemoteCompletedTurnSummary[];
|
|
3112
|
+
fallbackStart: number;
|
|
3113
|
+
}): number {
|
|
3114
|
+
try {
|
|
3115
|
+
this.store.importCompletedTurnHistory({
|
|
3116
|
+
localSessionId: input.localSessionId,
|
|
3117
|
+
contextKey: input.contextKey,
|
|
3118
|
+
agentName: input.agentName,
|
|
3119
|
+
remoteSessionId: input.remoteSessionId,
|
|
3120
|
+
turns: input.completedTurns.map((turn, index) => {
|
|
3121
|
+
const startedAt = turn.startedAt ?? input.fallbackStart + index;
|
|
3122
|
+
const completedAt = turn.completedAt ?? startedAt;
|
|
3123
|
+
return {
|
|
3124
|
+
turnId: turn.id,
|
|
3125
|
+
snapshot: {
|
|
3126
|
+
sessionId: input.localSessionId,
|
|
3127
|
+
turnId: turn.id,
|
|
3128
|
+
prompt: turn.prompt ?? "未记录对话内容",
|
|
3129
|
+
status: "completed",
|
|
3130
|
+
startedAt,
|
|
3131
|
+
completedAt,
|
|
3132
|
+
assistantText: "",
|
|
3133
|
+
plan: [],
|
|
3134
|
+
activities: [],
|
|
3135
|
+
completedTools: [],
|
|
3136
|
+
failedTools: [],
|
|
3137
|
+
fileSummary: [],
|
|
3138
|
+
} satisfies TurnViewState,
|
|
3139
|
+
updatedAt: new Date(completedAt).toISOString(),
|
|
3140
|
+
};
|
|
3141
|
+
}),
|
|
3142
|
+
});
|
|
3143
|
+
return input.completedTurns.length;
|
|
3144
|
+
} catch (error) {
|
|
3145
|
+
this.logger.warn(
|
|
3146
|
+
{ error, remoteSessionId: input.remoteSessionId },
|
|
3147
|
+
"Failed to persist inherited Turn history after Fork; keeping the Fork anchor fallback.",
|
|
3148
|
+
);
|
|
3149
|
+
return 0;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
private async hydrateLegacyForkTurnHistory(
|
|
3154
|
+
contextKey: string,
|
|
3155
|
+
current: SessionRecord,
|
|
3156
|
+
): Promise<void> {
|
|
3157
|
+
const source = this.store.getForkHistorySource(current.localSessionId);
|
|
3158
|
+
if (!source || source.sourceTurnHistoryCount !== undefined
|
|
3159
|
+
|| this.store.hasImportedForkTurnHistory(current.localSessionId)) return;
|
|
3160
|
+
if (!source.sourceRemoteSessionId) return;
|
|
3161
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
3162
|
+
if (runtime.kind !== "codex" || !runtime.readRemoteSession) return;
|
|
3163
|
+
try {
|
|
3164
|
+
const remote = await runtime.readRemoteSession(source.sourceRemoteSessionId);
|
|
3165
|
+
const completedTurns = remote.completedTurns ?? [];
|
|
3166
|
+
const anchorIndex = completedTurns.findIndex((turn) => turn.id === source.sourceTurnId);
|
|
3167
|
+
if (anchorIndex < 0) return;
|
|
3168
|
+
const inheritedTurns = completedTurns.slice(0, anchorIndex + 1);
|
|
3169
|
+
const sourceSession = source.sourceLocalSessionId
|
|
3170
|
+
? this.store.getSession(source.sourceLocalSessionId)
|
|
3171
|
+
: undefined;
|
|
3172
|
+
const imported = this.persistCompletedTurnHistory({
|
|
3173
|
+
localSessionId: sourceSession?.localSessionId ?? current.localSessionId,
|
|
3174
|
+
contextKey: sourceSession?.contextKey ?? contextKey,
|
|
3175
|
+
agentName: current.agentName,
|
|
3176
|
+
remoteSessionId: source.sourceRemoteSessionId,
|
|
3177
|
+
completedTurns: inheritedTurns,
|
|
3178
|
+
fallbackStart: source.sourceTurnStartedAt
|
|
3179
|
+
?? source.sourceTurnCompletedAt
|
|
3180
|
+
?? Date.now() - inheritedTurns.length,
|
|
3181
|
+
});
|
|
3182
|
+
if (imported > 0) {
|
|
3183
|
+
this.store.audit(contextKey, "fork_turn_history_imported", {
|
|
3184
|
+
forkedLocalSessionId: current.localSessionId,
|
|
3185
|
+
sourceRemoteSessionId: source.sourceRemoteSessionId,
|
|
3186
|
+
sourceTurnId: source.sourceTurnId,
|
|
3187
|
+
importedTurnCount: imported,
|
|
3188
|
+
});
|
|
3189
|
+
}
|
|
3190
|
+
} catch (error) {
|
|
3191
|
+
this.logger.warn(
|
|
3192
|
+
{ error, localSessionId: current.localSessionId, sourceRemoteSessionId: source.sourceRemoteSessionId },
|
|
3193
|
+
"Failed to hydrate legacy Fork Turn history; rendering the locally available history.",
|
|
3194
|
+
);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3008
3198
|
private async createProjectSessionFromReference(contextKey: string, reference: string): Promise<void> {
|
|
3009
3199
|
const resolved = await this.resolveProjectSessionReference(contextKey, reference);
|
|
3010
3200
|
const created = await this.createSession(
|
|
@@ -3220,19 +3410,21 @@ export class ProxySessionController {
|
|
|
3220
3410
|
executionSettings: SessionExecutionSettings = {},
|
|
3221
3411
|
): Promise<SessionRecord> {
|
|
3222
3412
|
const agent = this.ensureAgent(agentName);
|
|
3413
|
+
const resolvedExecutionSettings = mergeExecutionSettings(agent.defaults ?? {}, executionSettings);
|
|
3223
3414
|
const localSessionId = createId("sess");
|
|
3224
3415
|
const initialTitle = normalizeTaskTitle(requestedTitle ?? prompt ?? "");
|
|
3225
3416
|
const sessionCwd = cwd === undefined && agent.kind === "app-server"
|
|
3226
3417
|
? createProjectlessWorkspace({ prompt: initialTitle }).cwd
|
|
3227
3418
|
: path.resolve(cwd ?? this.config.defaults.cwd);
|
|
3228
3419
|
const record = this.store.createSession({ localSessionId, contextKey, agentName, cwd: sessionCwd, status: "starting" });
|
|
3229
|
-
if (initialTitle ||
|
|
3420
|
+
if (initialTitle || resolvedExecutionSettings.modelProvider || resolvedExecutionSettings.model
|
|
3421
|
+
|| resolvedExecutionSettings.reasoningEffort || resolvedExecutionSettings.permissionMode) {
|
|
3230
3422
|
this.store.updateRuntimeSession(localSessionId, {
|
|
3231
3423
|
title: initialTitle,
|
|
3232
|
-
modelProvider:
|
|
3233
|
-
model:
|
|
3234
|
-
reasoningEffort:
|
|
3235
|
-
permissionMode:
|
|
3424
|
+
modelProvider: resolvedExecutionSettings.modelProvider,
|
|
3425
|
+
model: resolvedExecutionSettings.model,
|
|
3426
|
+
reasoningEffort: resolvedExecutionSettings.reasoningEffort,
|
|
3427
|
+
permissionMode: resolvedExecutionSettings.permissionMode,
|
|
3236
3428
|
});
|
|
3237
3429
|
}
|
|
3238
3430
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
@@ -3253,10 +3445,10 @@ export class ProxySessionController {
|
|
|
3253
3445
|
agentName,
|
|
3254
3446
|
cwd: sessionCwd,
|
|
3255
3447
|
title: initialTitle,
|
|
3256
|
-
modelProvider:
|
|
3257
|
-
model:
|
|
3258
|
-
reasoningEffort:
|
|
3259
|
-
permissionMode:
|
|
3448
|
+
modelProvider: resolvedExecutionSettings.modelProvider,
|
|
3449
|
+
model: resolvedExecutionSettings.model,
|
|
3450
|
+
reasoningEffort: resolvedExecutionSettings.reasoningEffort,
|
|
3451
|
+
permissionMode: resolvedExecutionSettings.permissionMode ?? "auto",
|
|
3260
3452
|
});
|
|
3261
3453
|
this.persistRuntimeSession(record, session, session.activeTurnId ? "running" : "ready");
|
|
3262
3454
|
const saved = this.store.getSession(localSessionId) ?? record;
|
|
@@ -4091,6 +4283,53 @@ export class ProxySessionController {
|
|
|
4091
4283
|
}
|
|
4092
4284
|
}
|
|
4093
4285
|
|
|
4286
|
+
private async resetFromHistoryCard(
|
|
4287
|
+
contextKey: string,
|
|
4288
|
+
sessionId: string,
|
|
4289
|
+
turnId: string,
|
|
4290
|
+
updateMessageId: string,
|
|
4291
|
+
page: number,
|
|
4292
|
+
): Promise<void> {
|
|
4293
|
+
if (!sessionId || !turnId) throw new Error("无效的 Reset 请求。请使用最新的历史轮次卡片重试。");
|
|
4294
|
+
const existingTurnId = this.resetHistoryOperations.get(sessionId);
|
|
4295
|
+
if (existingTurnId) {
|
|
4296
|
+
await this.openResetHistory(contextKey, { expectedSessionId: sessionId, updateMessageId, page });
|
|
4297
|
+
throw new Error("当前已有 Reset 正在执行,请稍候。");
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
this.resetHistoryOperations.set(sessionId, turnId);
|
|
4301
|
+
try {
|
|
4302
|
+
await this.openResetHistory(contextKey, { expectedSessionId: sessionId, updateMessageId, page });
|
|
4303
|
+
} catch (error) {
|
|
4304
|
+
if (this.resetHistoryOperations.get(sessionId) === turnId) {
|
|
4305
|
+
this.resetHistoryOperations.delete(sessionId);
|
|
4306
|
+
}
|
|
4307
|
+
throw error;
|
|
4308
|
+
}
|
|
4309
|
+
|
|
4310
|
+
try {
|
|
4311
|
+
await this.resetCurrentSessionToTurn(contextKey, sessionId, turnId);
|
|
4312
|
+
} catch (error) {
|
|
4313
|
+
if (this.resetHistoryOperations.get(sessionId) === turnId) {
|
|
4314
|
+
this.resetHistoryOperations.delete(sessionId);
|
|
4315
|
+
}
|
|
4316
|
+
try {
|
|
4317
|
+
await this.openResetHistory(contextKey, { expectedSessionId: sessionId, updateMessageId, page });
|
|
4318
|
+
} catch (refreshError) {
|
|
4319
|
+
this.logger.warn(
|
|
4320
|
+
{ error: refreshError, contextKey, sessionId, turnId, updateMessageId },
|
|
4321
|
+
"Failed to restore the Turns card after Reset failed.",
|
|
4322
|
+
);
|
|
4323
|
+
}
|
|
4324
|
+
throw error;
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
if (this.resetHistoryOperations.get(sessionId) === turnId) {
|
|
4328
|
+
this.resetHistoryOperations.delete(sessionId);
|
|
4329
|
+
}
|
|
4330
|
+
await this.openResetHistory(contextKey, { expectedSessionId: sessionId, updateMessageId, page });
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4094
4333
|
private async performCurrentSessionReset(
|
|
4095
4334
|
contextKey: string,
|
|
4096
4335
|
sessionId: string,
|
|
@@ -4430,12 +4669,13 @@ export class ProxySessionController {
|
|
|
4430
4669
|
reasoningEffort: session.reasoningEffort ?? settings.effort,
|
|
4431
4670
|
permissionMode: session.permissionMode,
|
|
4432
4671
|
});
|
|
4672
|
+
await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
|
|
4433
4673
|
const notice = [
|
|
4434
4674
|
`Provider 已切换为 ${cardCode(session.modelProvider ?? settings.provider)}`,
|
|
4435
4675
|
`模型 ${cardCode(session.model ?? settings.model)}`,
|
|
4436
4676
|
`思考强度 ${cardCode(session.reasoningEffort ?? settings.effort)}`,
|
|
4437
4677
|
`权限 ${cardCode(session.permissionMode)}`,
|
|
4438
|
-
|
|
4678
|
+
`并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
|
|
4439
4679
|
].join(",");
|
|
4440
4680
|
if (options.updateMessageId) {
|
|
4441
4681
|
await this.openProviderSelector(contextKey, {
|
|
@@ -4505,6 +4745,7 @@ export class ProxySessionController {
|
|
|
4505
4745
|
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, nextEffort);
|
|
4506
4746
|
}
|
|
4507
4747
|
this.store.updateRuntimeSession(loaded.record.localSessionId, { model, reasoningEffort: nextEffort });
|
|
4748
|
+
await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
|
|
4508
4749
|
const effortMessage = nextEffort && nextEffort !== currentEffort
|
|
4509
4750
|
? `,思考强度已自动调整为 ${nextEffort}`
|
|
4510
4751
|
: "";
|
|
@@ -4512,11 +4753,14 @@ export class ProxySessionController {
|
|
|
4512
4753
|
await this.openExecutionSettings(contextKey, "model", {
|
|
4513
4754
|
sessionId: loaded.record.localSessionId,
|
|
4514
4755
|
updateMessageId: options.updateMessageId,
|
|
4515
|
-
notice: `模型已切换为 ${cardCode(model)}${effortMessage}
|
|
4756
|
+
notice: `模型已切换为 ${cardCode(model)}${effortMessage},并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
|
|
4516
4757
|
});
|
|
4517
4758
|
return;
|
|
4518
4759
|
}
|
|
4519
|
-
await this.outbound.sendText(
|
|
4760
|
+
await this.outbound.sendText(
|
|
4761
|
+
contextKey,
|
|
4762
|
+
`模型已切换为 ${model}${effortMessage},并已保存为 ${loaded.record.agentName} Agent 的默认设置,从下一次请求生效。`,
|
|
4763
|
+
);
|
|
4520
4764
|
}
|
|
4521
4765
|
|
|
4522
4766
|
private async goal(contextKey: string, command: Extract<Command, { type: "goal" }>): Promise<void> {
|
|
@@ -4899,15 +5143,19 @@ export class ProxySessionController {
|
|
|
4899
5143
|
}
|
|
4900
5144
|
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, effort);
|
|
4901
5145
|
this.store.updateRuntimeSession(loaded.record.localSessionId, { reasoningEffort: effort });
|
|
5146
|
+
await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
|
|
4902
5147
|
if (options.updateMessageId) {
|
|
4903
5148
|
await this.openExecutionSettings(contextKey, "thinking", {
|
|
4904
5149
|
sessionId: loaded.record.localSessionId,
|
|
4905
5150
|
updateMessageId: options.updateMessageId,
|
|
4906
|
-
notice: `思考强度已切换为 ${cardCode(effort)}
|
|
5151
|
+
notice: `思考强度已切换为 ${cardCode(effort)},并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
|
|
4907
5152
|
});
|
|
4908
5153
|
return;
|
|
4909
5154
|
}
|
|
4910
|
-
await this.outbound.sendText(
|
|
5155
|
+
await this.outbound.sendText(
|
|
5156
|
+
contextKey,
|
|
5157
|
+
`思考强度已切换为 ${effort},并已保存为 ${loaded.record.agentName} Agent 的默认设置,从下一次请求生效。`,
|
|
5158
|
+
);
|
|
4911
5159
|
}
|
|
4912
5160
|
|
|
4913
5161
|
private async permissions(
|
|
@@ -4926,17 +5174,23 @@ export class ProxySessionController {
|
|
|
4926
5174
|
await loaded.runtime.setPermissionMode(record.localSessionId, mode);
|
|
4927
5175
|
loaded.session.permissionMode = mode;
|
|
4928
5176
|
this.store.updateRuntimeSession(record.localSessionId, { permissionMode: mode });
|
|
5177
|
+
await this.persistAgentExecutionDefaults(record.localSessionId);
|
|
4929
5178
|
if (options.updateMessageId) {
|
|
4930
5179
|
await this.openExecutionSettings(contextKey, "permission", {
|
|
4931
5180
|
sessionId: loaded.record.localSessionId,
|
|
4932
5181
|
updateMessageId: options.updateMessageId,
|
|
4933
5182
|
notice: mode === "auto"
|
|
4934
|
-
?
|
|
4935
|
-
:
|
|
5183
|
+
? `已切换为自动执行模式,并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`
|
|
5184
|
+
: `已切换为执行前确认模式,并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
|
|
4936
5185
|
});
|
|
4937
5186
|
return;
|
|
4938
5187
|
}
|
|
4939
|
-
await this.outbound.sendText(
|
|
5188
|
+
await this.outbound.sendText(
|
|
5189
|
+
contextKey,
|
|
5190
|
+
mode === "auto"
|
|
5191
|
+
? `已切换为自动执行模式,并已保存为 ${loaded.record.agentName} Agent 的默认设置。`
|
|
5192
|
+
: `已切换为执行前确认模式,并已保存为 ${loaded.record.agentName} Agent 的默认设置。`,
|
|
5193
|
+
);
|
|
4940
5194
|
}
|
|
4941
5195
|
|
|
4942
5196
|
private async openResetHistory(
|
|
@@ -4950,6 +5204,7 @@ export class ProxySessionController {
|
|
|
4950
5204
|
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
4951
5205
|
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
4952
5206
|
}
|
|
5207
|
+
await this.hydrateLegacyForkTurnHistory(contextKey, current);
|
|
4953
5208
|
const completedTurns = this.store.listTaskTurnGraph(current.localSessionId);
|
|
4954
5209
|
const completedTurnIds = new Set(completedTurns.map((turn) => turn.turnId));
|
|
4955
5210
|
const runningTurnId = current.lastTurnStatus === "running" && current.lastTurnId
|
|
@@ -4996,6 +5251,7 @@ export class ProxySessionController {
|
|
|
4996
5251
|
const page = Math.max(0, Math.min(Math.trunc(options.page ?? 0), totalPages - 1));
|
|
4997
5252
|
const offset = page * RESET_HISTORY_PAGE_SIZE;
|
|
4998
5253
|
const turns = allTurns.slice(offset, offset + RESET_HISTORY_PAGE_SIZE);
|
|
5254
|
+
const resettingTurnId = this.resetHistoryOperations.get(current.localSessionId);
|
|
4999
5255
|
const entries: ResetHistoryCardEntry[] = turns.flatMap((turn, index) => {
|
|
5000
5256
|
const snapshot = turnViewSnapshot(turn.snapshot);
|
|
5001
5257
|
if (!snapshot) return [];
|
|
@@ -5006,17 +5262,17 @@ export class ProxySessionController {
|
|
|
5006
5262
|
);
|
|
5007
5263
|
const isCurrent = current.lastTurnId === turn.turnId && current.lastTurnStatus === "completed";
|
|
5008
5264
|
const isRunning = runningTurnId === turn.turnId;
|
|
5265
|
+
const isResetting = resettingTurnId === turn.turnId;
|
|
5009
5266
|
return [{
|
|
5010
5267
|
sequence: graph.sequence,
|
|
5011
5268
|
graphNodeLine: graph.nodeLine,
|
|
5012
5269
|
graphConnectorLine: graph.connectorLine,
|
|
5013
|
-
lines: [
|
|
5014
|
-
|
|
5015
|
-
`${formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt)} · ${cardText(turn.turnId)}`,
|
|
5016
|
-
],
|
|
5270
|
+
lines: [cardText(summary || "未记录对话内容")],
|
|
5271
|
+
timestamp: formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt),
|
|
5017
5272
|
current: isCurrent,
|
|
5018
5273
|
running: isRunning,
|
|
5019
|
-
|
|
5274
|
+
resetting: isResetting,
|
|
5275
|
+
actions: isCurrent || isRunning || resettingTurnId ? undefined : [{
|
|
5020
5276
|
text: "Reset",
|
|
5021
5277
|
value: {
|
|
5022
5278
|
action: "turn_reset",
|
|
@@ -5055,6 +5311,7 @@ export class ProxySessionController {
|
|
|
5055
5311
|
runningTurn
|
|
5056
5312
|
? `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个 turn(${completedTurns.length} 个已完成,1 个运行中)`
|
|
5057
5313
|
: `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个已完成 turn`,
|
|
5314
|
+
...(resettingTurnId ? ["正在 Reset 到所选轮次,请稍候…"] : []),
|
|
5058
5315
|
],
|
|
5059
5316
|
pageActions,
|
|
5060
5317
|
});
|
|
@@ -6179,6 +6436,21 @@ export class ProxySessionController {
|
|
|
6179
6436
|
return agent;
|
|
6180
6437
|
}
|
|
6181
6438
|
|
|
6439
|
+
private async persistAgentExecutionDefaults(localSessionId: string): Promise<void> {
|
|
6440
|
+
const session = this.store.getSession(localSessionId);
|
|
6441
|
+
if (!session) throw new Error(`找不到任务:${localSessionId}`);
|
|
6442
|
+
const agent = this.ensureAgent(session.agentName);
|
|
6443
|
+
const observed: AgentExecutionDefaults = {
|
|
6444
|
+
...(session.modelProvider?.trim() ? { modelProvider: session.modelProvider } : {}),
|
|
6445
|
+
...(session.model?.trim() ? { model: session.model } : {}),
|
|
6446
|
+
...(session.reasoningEffort?.trim() ? { reasoningEffort: session.reasoningEffort } : {}),
|
|
6447
|
+
...(session.permissionMode ? { permissionMode: session.permissionMode } : {}),
|
|
6448
|
+
};
|
|
6449
|
+
const defaults = { ...(agent.defaults ?? {}), ...observed };
|
|
6450
|
+
await this.lifecycle?.persistAgentExecutionDefaults?.(session.agentName, defaults);
|
|
6451
|
+
agent.defaults = defaults;
|
|
6452
|
+
}
|
|
6453
|
+
|
|
6182
6454
|
private agentLabel(agentName: string): string {
|
|
6183
6455
|
return this.ensureAgent(agentName).title;
|
|
6184
6456
|
}
|
|
@@ -6246,6 +6518,18 @@ function threadForkAnchorMessageIds(message: IncomingMessage): string[] {
|
|
|
6246
6518
|
.filter((messageId): messageId is string => Boolean(messageId && messageId !== message.messageId)))];
|
|
6247
6519
|
}
|
|
6248
6520
|
|
|
6521
|
+
function mergeExecutionSettings(
|
|
6522
|
+
defaults: AgentExecutionDefaults,
|
|
6523
|
+
overrides: SessionExecutionSettings,
|
|
6524
|
+
): SessionExecutionSettings {
|
|
6525
|
+
return {
|
|
6526
|
+
modelProvider: overrides.modelProvider ?? defaults.modelProvider,
|
|
6527
|
+
model: overrides.model ?? defaults.model,
|
|
6528
|
+
reasoningEffort: overrides.reasoningEffort ?? defaults.reasoningEffort,
|
|
6529
|
+
permissionMode: overrides.permissionMode ?? defaults.permissionMode,
|
|
6530
|
+
};
|
|
6531
|
+
}
|
|
6532
|
+
|
|
6249
6533
|
function sessionStatusLabel(status: SessionRecord["status"], activeTurnId?: string): string {
|
|
6250
6534
|
if (activeTurnId || status === "running") return "执行中";
|
|
6251
6535
|
const labels: Record<SessionRecord["status"], string> = {
|
|
@@ -6737,6 +7021,12 @@ function normalizeRemoteTimestamp(value: number): number {
|
|
|
6737
7021
|
return value >= 10_000_000_000 ? value : value * 1_000;
|
|
6738
7022
|
}
|
|
6739
7023
|
|
|
7024
|
+
function parseIsoTimestamp(value: string | undefined): number | undefined {
|
|
7025
|
+
if (!value) return undefined;
|
|
7026
|
+
const timestamp = Date.parse(value);
|
|
7027
|
+
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
7028
|
+
}
|
|
7029
|
+
|
|
6740
7030
|
function latestRemoteTimestamp(...values: Array<number | undefined>): number | undefined {
|
|
6741
7031
|
const timestamps = values
|
|
6742
7032
|
.filter((value): value is number => value !== undefined)
|
package/src/runtime/types.ts
CHANGED
|
@@ -106,6 +106,13 @@ export interface RuntimeSessionMetadata {
|
|
|
106
106
|
|
|
107
107
|
export type RemoteSessionStatus = "active" | "idle" | "not_loaded" | "error";
|
|
108
108
|
|
|
109
|
+
export interface RemoteCompletedTurnSummary {
|
|
110
|
+
id: string;
|
|
111
|
+
prompt?: string;
|
|
112
|
+
startedAt?: number;
|
|
113
|
+
completedAt?: number;
|
|
114
|
+
}
|
|
115
|
+
|
|
109
116
|
export interface RemoteSessionSummary {
|
|
110
117
|
id: string;
|
|
111
118
|
title?: string;
|
|
@@ -131,6 +138,7 @@ export interface RemoteSessionSummary {
|
|
|
131
138
|
lastTurnCompletedToolCount?: number;
|
|
132
139
|
lastTurnFailedToolCount?: number;
|
|
133
140
|
lastTurnRunningToolCount?: number;
|
|
141
|
+
completedTurns?: RemoteCompletedTurnSummary[];
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
export interface RemoteSessionPage {
|