@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +4 -2
  3. package/README.zh.md +4 -2
  4. package/config.example.yaml +8 -0
  5. package/dist/cli/help.js +4 -4
  6. package/dist/cli/taskTarget.js +7 -0
  7. package/dist/cli/taskTarget.js.map +1 -1
  8. package/dist/cli.js +12 -12
  9. package/dist/cli.js.map +1 -1
  10. package/dist/codex/CodexRuntime.js +40 -12
  11. package/dist/codex/CodexRuntime.js.map +1 -1
  12. package/dist/config/AgentExecutionDefaultsStore.js +71 -0
  13. package/dist/config/AgentExecutionDefaultsStore.js.map +1 -0
  14. package/dist/config/loadConfig.js +8 -0
  15. package/dist/config/loadConfig.js.map +1 -1
  16. package/dist/config/schema.js +7 -0
  17. package/dist/config/schema.js.map +1 -1
  18. package/dist/feishu/CardRenderer.js +29 -27
  19. package/dist/feishu/CardRenderer.js.map +1 -1
  20. package/dist/index.js +17 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/proxy/ProxySessionController.js +255 -30
  23. package/dist/proxy/ProxySessionController.js.map +1 -1
  24. package/dist/state/StateStore.js +62 -9
  25. package/dist/state/StateStore.js.map +1 -1
  26. package/dist/supervision/restartNotificationTarget.js +9 -0
  27. package/dist/supervision/restartNotificationTarget.js.map +1 -0
  28. package/docs/technical-reference.md +7 -3
  29. package/docs/technical-reference.zh.md +7 -3
  30. package/npm-shrinkwrap.json +2 -2
  31. package/package.json +1 -1
  32. package/skills/agent-bot/SKILL.md +3 -1
  33. package/src/cli/help.ts +4 -4
  34. package/src/cli/taskTarget.ts +11 -0
  35. package/src/cli.ts +16 -20
  36. package/src/codex/CodexRuntime.ts +38 -10
  37. package/src/config/AgentExecutionDefaultsStore.ts +71 -0
  38. package/src/config/loadConfig.ts +8 -0
  39. package/src/config/schema.ts +13 -1
  40. package/src/feishu/CardRenderer.ts +30 -22
  41. package/src/index.ts +17 -2
  42. package/src/proxy/ProxySessionController.ts +330 -40
  43. package/src/runtime/types.ts +8 -0
  44. package/src/state/StateStore.ts +100 -9
  45. package/src/supervision/restartNotificationTarget.ts +12 -0
@@ -4,7 +4,7 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { createProjectlessWorkspace, detectProjectlessWorkspace } from "../codex/ProjectlessWorkspace.js";
6
6
  import { resolveUserPath } from "../config/paths.js";
7
- import { DEFAULT_GROUP_NAME_FORMAT } from "../config/schema.js";
7
+ import { DEFAULT_GROUP_NAME_FORMAT, } from "../config/schema.js";
8
8
  import { CommandRouter } from "../commands/CommandRouter.js";
9
9
  import { baseChatContextKey, isThreadContextKey } from "../feishu/contextKey.js";
10
10
  import { CardRenderer, } from "../feishu/CardRenderer.js";
@@ -189,6 +189,7 @@ export class ProxySessionController {
189
189
  queuedPromptCards = new Map();
190
190
  queuedPromptCardWrites = new Map();
191
191
  sessionResets = new Map();
192
+ resetHistoryOperations = new Map();
192
193
  lastSessionListings = new Map();
193
194
  threadInitializations = new Map();
194
195
  llmRetryingSessions = new Set();
@@ -513,13 +514,12 @@ export class ProxySessionController {
513
514
  }
514
515
  else if (kind === "turn_reset") {
515
516
  const sessionId = String(scopedAction.value.sessionId ?? "");
516
- await this.resetCurrentSessionToTurn(contextKey, sessionId, String(scopedAction.value.turnId ?? ""));
517
+ const turnId = String(scopedAction.value.turnId ?? "");
517
518
  if (scopedAction.value.cardView === "reset_history") {
518
- await this.openResetHistory(contextKey, {
519
- expectedSessionId: sessionId,
520
- updateMessageId: requiredCardMessageId(scopedAction.messageId),
521
- page: resetHistoryPageValue(scopedAction.value.page),
522
- });
519
+ await this.resetFromHistoryCard(contextKey, sessionId, turnId, requiredCardMessageId(scopedAction.messageId), resetHistoryPageValue(scopedAction.value.page));
520
+ }
521
+ else {
522
+ await this.resetCurrentSessionToTurn(contextKey, sessionId, turnId);
523
523
  }
524
524
  }
525
525
  else if (kind === "turn_reset_page") {
@@ -761,6 +761,7 @@ export class ProxySessionController {
761
761
  this.queuedPromptCards.clear();
762
762
  this.queuedPromptCardWrites.clear();
763
763
  this.sessionResets.clear();
764
+ this.resetHistoryOperations.clear();
764
765
  for (const pending of this.pendingMergedForwards.values())
765
766
  pending.resolveAttachment(undefined);
766
767
  this.pendingMergedForwards.clear();
@@ -1096,6 +1097,8 @@ export class ProxySessionController {
1096
1097
  this.store.updateRuntimeSession(record.localSessionId, { permissionMode: nextValue });
1097
1098
  }
1098
1099
  const session = this.store.getSession(record.localSessionId) ?? record;
1100
+ if (setting)
1101
+ await this.persistAgentExecutionDefaults(session.localSessionId);
1099
1102
  const models = await loaded.runtime.listModels();
1100
1103
  const currentModel = models.find((candidate) => candidate.id === session.model)
1101
1104
  ?? models.find((candidate) => candidate.isDefault);
@@ -2178,6 +2181,18 @@ export class ProxySessionController {
2178
2181
  const model = remote?.model ?? source?.model;
2179
2182
  const reasoningEffort = source?.reasoningEffort;
2180
2183
  const permissionMode = source?.permissionMode ?? "auto";
2184
+ const sourceTurnPrompt = truncateText(latestUserPromptFromTurnView(snapshot)
2185
+ ?? (lastTurnId === latestTurnId ? remote?.lastUserPrompt : undefined)
2186
+ ?? remote?.preview
2187
+ ?? source?.title
2188
+ ?? remote?.title
2189
+ ?? "Fork 来源轮次", 1_000);
2190
+ const sourceTurnCompletedAt = snapshot?.completedAt
2191
+ ?? snapshot?.startedAt
2192
+ ?? (lastTurnId === latestTurnId
2193
+ ? latestRemoteTimestamp(remote?.recencyAt, remote?.updatedAt)
2194
+ : undefined)
2195
+ ?? parseIsoTimestamp(source?.updatedAt);
2181
2196
  return {
2182
2197
  source,
2183
2198
  sourceLabel,
@@ -2196,6 +2211,10 @@ export class ProxySessionController {
2196
2211
  : mapRemoteTurnStatus(remote?.lastTurnStatus)
2197
2212
  ?? forkedTurnStatus(snapshot?.status)
2198
2213
  ?? source?.lastTurnStatus,
2214
+ sourceTurnPrompt,
2215
+ sourceTurnStartedAt: snapshot?.startedAt ?? sourceTurnCompletedAt,
2216
+ sourceTurnCompletedAt,
2217
+ sourceCompletedTurns: remote?.completedTurns ?? [],
2199
2218
  sourceWasRunning: isRunning,
2200
2219
  forkedFromHistoricalTurn,
2201
2220
  };
@@ -2242,6 +2261,9 @@ export class ProxySessionController {
2242
2261
  || source.lastTurnStatus === "running"
2243
2262
  || source.status === "running");
2244
2263
  const forkTitle = normalizeTaskTitle(requestedTitle) ?? this.store.nextForkTitle(source.title);
2264
+ const sourceTurnCompletedAt = snapshot?.completedAt
2265
+ ?? snapshot?.startedAt
2266
+ ?? parseIsoTimestamp(source.updatedAt);
2245
2267
  return {
2246
2268
  source,
2247
2269
  sourceLabel,
@@ -2258,11 +2280,15 @@ export class ProxySessionController {
2258
2280
  lastTurnStatus: forkedTurnStatus(snapshot?.status)
2259
2281
  ?? (source.lastTurnId === lastTurnId ? source.lastTurnStatus : undefined)
2260
2282
  ?? "completed",
2283
+ sourceTurnPrompt: truncateText(latestUserPromptFromTurnView(snapshot) ?? source.title ?? "Fork 来源轮次", 1_000),
2284
+ sourceTurnStartedAt: snapshot?.startedAt ?? sourceTurnCompletedAt,
2285
+ sourceTurnCompletedAt,
2261
2286
  sourceWasRunning,
2262
2287
  forkedFromHistoricalTurn: sourceWasRunning && source.lastTurnId !== lastTurnId,
2263
2288
  };
2264
2289
  }
2265
2290
  async forkSessionIntoContext(contextKey, plan) {
2291
+ const sourceCompletedTurns = await this.resolveForkSourceCompletedTurns(plan);
2266
2292
  const localSessionId = createId("sess");
2267
2293
  const record = this.store.createSession({
2268
2294
  localSessionId,
@@ -2297,16 +2323,21 @@ export class ProxySessionController {
2297
2323
  lastTurnId: plan.lastTurnId,
2298
2324
  lastTurnStatus: plan.lastTurnStatus,
2299
2325
  });
2300
- this.store.setCurrentSession(contextKey, localSessionId);
2301
2326
  this.outbound.registerSession(localSessionId, contextKey, forked.title ?? plan.forkTitle, plan.cwd, this.agentLabel(plan.agentName));
2327
+ const sourceTurnHistoryCount = this.persistForkSourceHistory(contextKey, localSessionId, plan, sourceCompletedTurns);
2302
2328
  this.store.audit(contextKey, "session_forked", {
2303
2329
  sourceLocalSessionId: plan.source?.localSessionId,
2304
2330
  sourceRemoteSessionId: plan.remoteSessionId,
2305
2331
  sourceTurnId: plan.lastTurnId,
2332
+ sourceTurnPrompt: plan.sourceTurnPrompt,
2333
+ sourceTurnStartedAt: plan.sourceTurnStartedAt,
2334
+ sourceTurnCompletedAt: plan.sourceTurnCompletedAt,
2335
+ sourceTurnHistoryCount,
2306
2336
  sourceWasRunning: plan.sourceWasRunning,
2307
2337
  forkedLocalSessionId: localSessionId,
2308
2338
  forkedRemoteSessionId: forked.remoteSessionId,
2309
2339
  });
2340
+ this.store.setCurrentSession(contextKey, localSessionId);
2310
2341
  return {
2311
2342
  record: this.store.getSession(localSessionId) ?? record,
2312
2343
  session: forked,
@@ -2317,6 +2348,122 @@ export class ProxySessionController {
2317
2348
  throw error;
2318
2349
  }
2319
2350
  }
2351
+ async resolveForkSourceCompletedTurns(plan) {
2352
+ let completedTurns = plan.sourceCompletedTurns;
2353
+ if (completedTurns === undefined && plan.runtime.readRemoteSession) {
2354
+ try {
2355
+ completedTurns = (await plan.runtime.readRemoteSession(plan.remoteSessionId)).completedTurns ?? [];
2356
+ }
2357
+ catch (error) {
2358
+ this.logger.warn({ error, remoteSessionId: plan.remoteSessionId, sourceTurnId: plan.lastTurnId }, "Failed to read the source task Turn history before Fork; keeping the Fork anchor fallback.");
2359
+ }
2360
+ }
2361
+ if (!completedTurns?.length)
2362
+ return [];
2363
+ const anchorIndex = completedTurns.findIndex((turn) => turn.id === plan.lastTurnId);
2364
+ if (anchorIndex < 0) {
2365
+ this.logger.warn({ remoteSessionId: plan.remoteSessionId, sourceTurnId: plan.lastTurnId }, "The source task Turn history did not include the Fork anchor; keeping the Fork anchor fallback.");
2366
+ return [];
2367
+ }
2368
+ return completedTurns.slice(0, anchorIndex + 1);
2369
+ }
2370
+ persistForkSourceHistory(contextKey, forkedLocalSessionId, plan, completedTurns) {
2371
+ if (completedTurns.length === 0)
2372
+ return 0;
2373
+ const localSessionId = plan.source?.localSessionId ?? forkedLocalSessionId;
2374
+ const historyContextKey = plan.source?.contextKey ?? contextKey;
2375
+ const fallbackStart = plan.sourceTurnStartedAt
2376
+ ?? plan.sourceTurnCompletedAt
2377
+ ?? Date.now() - completedTurns.length;
2378
+ return this.persistCompletedTurnHistory({
2379
+ localSessionId,
2380
+ contextKey: historyContextKey,
2381
+ agentName: plan.agentName,
2382
+ remoteSessionId: plan.remoteSessionId,
2383
+ completedTurns,
2384
+ fallbackStart,
2385
+ });
2386
+ }
2387
+ persistCompletedTurnHistory(input) {
2388
+ try {
2389
+ this.store.importCompletedTurnHistory({
2390
+ localSessionId: input.localSessionId,
2391
+ contextKey: input.contextKey,
2392
+ agentName: input.agentName,
2393
+ remoteSessionId: input.remoteSessionId,
2394
+ turns: input.completedTurns.map((turn, index) => {
2395
+ const startedAt = turn.startedAt ?? input.fallbackStart + index;
2396
+ const completedAt = turn.completedAt ?? startedAt;
2397
+ return {
2398
+ turnId: turn.id,
2399
+ snapshot: {
2400
+ sessionId: input.localSessionId,
2401
+ turnId: turn.id,
2402
+ prompt: turn.prompt ?? "未记录对话内容",
2403
+ status: "completed",
2404
+ startedAt,
2405
+ completedAt,
2406
+ assistantText: "",
2407
+ plan: [],
2408
+ activities: [],
2409
+ completedTools: [],
2410
+ failedTools: [],
2411
+ fileSummary: [],
2412
+ },
2413
+ updatedAt: new Date(completedAt).toISOString(),
2414
+ };
2415
+ }),
2416
+ });
2417
+ return input.completedTurns.length;
2418
+ }
2419
+ catch (error) {
2420
+ this.logger.warn({ error, remoteSessionId: input.remoteSessionId }, "Failed to persist inherited Turn history after Fork; keeping the Fork anchor fallback.");
2421
+ return 0;
2422
+ }
2423
+ }
2424
+ async hydrateLegacyForkTurnHistory(contextKey, current) {
2425
+ const source = this.store.getForkHistorySource(current.localSessionId);
2426
+ if (!source || source.sourceTurnHistoryCount !== undefined
2427
+ || this.store.hasImportedForkTurnHistory(current.localSessionId))
2428
+ return;
2429
+ if (!source.sourceRemoteSessionId)
2430
+ return;
2431
+ const runtime = this.runtimes.forAgent(current.agentName);
2432
+ if (runtime.kind !== "codex" || !runtime.readRemoteSession)
2433
+ return;
2434
+ try {
2435
+ const remote = await runtime.readRemoteSession(source.sourceRemoteSessionId);
2436
+ const completedTurns = remote.completedTurns ?? [];
2437
+ const anchorIndex = completedTurns.findIndex((turn) => turn.id === source.sourceTurnId);
2438
+ if (anchorIndex < 0)
2439
+ return;
2440
+ const inheritedTurns = completedTurns.slice(0, anchorIndex + 1);
2441
+ const sourceSession = source.sourceLocalSessionId
2442
+ ? this.store.getSession(source.sourceLocalSessionId)
2443
+ : undefined;
2444
+ const imported = this.persistCompletedTurnHistory({
2445
+ localSessionId: sourceSession?.localSessionId ?? current.localSessionId,
2446
+ contextKey: sourceSession?.contextKey ?? contextKey,
2447
+ agentName: current.agentName,
2448
+ remoteSessionId: source.sourceRemoteSessionId,
2449
+ completedTurns: inheritedTurns,
2450
+ fallbackStart: source.sourceTurnStartedAt
2451
+ ?? source.sourceTurnCompletedAt
2452
+ ?? Date.now() - inheritedTurns.length,
2453
+ });
2454
+ if (imported > 0) {
2455
+ this.store.audit(contextKey, "fork_turn_history_imported", {
2456
+ forkedLocalSessionId: current.localSessionId,
2457
+ sourceRemoteSessionId: source.sourceRemoteSessionId,
2458
+ sourceTurnId: source.sourceTurnId,
2459
+ importedTurnCount: imported,
2460
+ });
2461
+ }
2462
+ }
2463
+ catch (error) {
2464
+ this.logger.warn({ error, localSessionId: current.localSessionId, sourceRemoteSessionId: source.sourceRemoteSessionId }, "Failed to hydrate legacy Fork Turn history; rendering the locally available history.");
2465
+ }
2466
+ }
2320
2467
  async createProjectSessionFromReference(contextKey, reference) {
2321
2468
  const resolved = await this.resolveProjectSessionReference(contextKey, reference);
2322
2469
  const created = await this.createSession(contextKey, resolved.agentName, resolved.cwd, true, false, undefined, undefined, undefined, resolved.executionSettings);
@@ -2488,19 +2635,21 @@ export class ProxySessionController {
2488
2635
  }
2489
2636
  async createSession(contextKey, agentName, cwd, announce, prepareTurn, prompt, replyTarget, requestedTitle, executionSettings = {}) {
2490
2637
  const agent = this.ensureAgent(agentName);
2638
+ const resolvedExecutionSettings = mergeExecutionSettings(agent.defaults ?? {}, executionSettings);
2491
2639
  const localSessionId = createId("sess");
2492
2640
  const initialTitle = normalizeTaskTitle(requestedTitle ?? prompt ?? "");
2493
2641
  const sessionCwd = cwd === undefined && agent.kind === "app-server"
2494
2642
  ? createProjectlessWorkspace({ prompt: initialTitle }).cwd
2495
2643
  : path.resolve(cwd ?? this.config.defaults.cwd);
2496
2644
  const record = this.store.createSession({ localSessionId, contextKey, agentName, cwd: sessionCwd, status: "starting" });
2497
- if (initialTitle || executionSettings.modelProvider || executionSettings.model || executionSettings.reasoningEffort || executionSettings.permissionMode) {
2645
+ if (initialTitle || resolvedExecutionSettings.modelProvider || resolvedExecutionSettings.model
2646
+ || resolvedExecutionSettings.reasoningEffort || resolvedExecutionSettings.permissionMode) {
2498
2647
  this.store.updateRuntimeSession(localSessionId, {
2499
2648
  title: initialTitle,
2500
- modelProvider: executionSettings.modelProvider,
2501
- model: executionSettings.model,
2502
- reasoningEffort: executionSettings.reasoningEffort,
2503
- permissionMode: executionSettings.permissionMode,
2649
+ modelProvider: resolvedExecutionSettings.modelProvider,
2650
+ model: resolvedExecutionSettings.model,
2651
+ reasoningEffort: resolvedExecutionSettings.reasoningEffort,
2652
+ permissionMode: resolvedExecutionSettings.permissionMode,
2504
2653
  });
2505
2654
  }
2506
2655
  this.store.setCurrentSession(contextKey, localSessionId);
@@ -2515,10 +2664,10 @@ export class ProxySessionController {
2515
2664
  agentName,
2516
2665
  cwd: sessionCwd,
2517
2666
  title: initialTitle,
2518
- modelProvider: executionSettings.modelProvider,
2519
- model: executionSettings.model,
2520
- reasoningEffort: executionSettings.reasoningEffort,
2521
- permissionMode: executionSettings.permissionMode ?? "auto",
2667
+ modelProvider: resolvedExecutionSettings.modelProvider,
2668
+ model: resolvedExecutionSettings.model,
2669
+ reasoningEffort: resolvedExecutionSettings.reasoningEffort,
2670
+ permissionMode: resolvedExecutionSettings.permissionMode ?? "auto",
2522
2671
  });
2523
2672
  this.persistRuntimeSession(record, session, session.activeTurnId ? "running" : "ready");
2524
2673
  const saved = this.store.getSession(localSessionId) ?? record;
@@ -3167,6 +3316,44 @@ export class ProxySessionController {
3167
3316
  this.sessionResets.delete(sessionId);
3168
3317
  }
3169
3318
  }
3319
+ async resetFromHistoryCard(contextKey, sessionId, turnId, updateMessageId, page) {
3320
+ if (!sessionId || !turnId)
3321
+ throw new Error("无效的 Reset 请求。请使用最新的历史轮次卡片重试。");
3322
+ const existingTurnId = this.resetHistoryOperations.get(sessionId);
3323
+ if (existingTurnId) {
3324
+ await this.openResetHistory(contextKey, { expectedSessionId: sessionId, updateMessageId, page });
3325
+ throw new Error("当前已有 Reset 正在执行,请稍候。");
3326
+ }
3327
+ this.resetHistoryOperations.set(sessionId, turnId);
3328
+ try {
3329
+ await this.openResetHistory(contextKey, { expectedSessionId: sessionId, updateMessageId, page });
3330
+ }
3331
+ catch (error) {
3332
+ if (this.resetHistoryOperations.get(sessionId) === turnId) {
3333
+ this.resetHistoryOperations.delete(sessionId);
3334
+ }
3335
+ throw error;
3336
+ }
3337
+ try {
3338
+ await this.resetCurrentSessionToTurn(contextKey, sessionId, turnId);
3339
+ }
3340
+ catch (error) {
3341
+ if (this.resetHistoryOperations.get(sessionId) === turnId) {
3342
+ this.resetHistoryOperations.delete(sessionId);
3343
+ }
3344
+ try {
3345
+ await this.openResetHistory(contextKey, { expectedSessionId: sessionId, updateMessageId, page });
3346
+ }
3347
+ catch (refreshError) {
3348
+ this.logger.warn({ error: refreshError, contextKey, sessionId, turnId, updateMessageId }, "Failed to restore the Turns card after Reset failed.");
3349
+ }
3350
+ throw error;
3351
+ }
3352
+ if (this.resetHistoryOperations.get(sessionId) === turnId) {
3353
+ this.resetHistoryOperations.delete(sessionId);
3354
+ }
3355
+ await this.openResetHistory(contextKey, { expectedSessionId: sessionId, updateMessageId, page });
3356
+ }
3170
3357
  async performCurrentSessionReset(contextKey, sessionId, turnId) {
3171
3358
  const current = this.requireCurrentSession(contextKey);
3172
3359
  if (current.localSessionId !== sessionId) {
@@ -3429,12 +3616,13 @@ export class ProxySessionController {
3429
3616
  reasoningEffort: session.reasoningEffort ?? settings.effort,
3430
3617
  permissionMode: session.permissionMode,
3431
3618
  });
3619
+ await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
3432
3620
  const notice = [
3433
3621
  `Provider 已切换为 ${cardCode(session.modelProvider ?? settings.provider)}`,
3434
3622
  `模型 ${cardCode(session.model ?? settings.model)}`,
3435
3623
  `思考强度 ${cardCode(session.reasoningEffort ?? settings.effort)}`,
3436
3624
  `权限 ${cardCode(session.permissionMode)}`,
3437
- "从下一次请求生效。",
3625
+ `并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
3438
3626
  ].join(",");
3439
3627
  if (options.updateMessageId) {
3440
3628
  await this.openProviderSelector(contextKey, {
@@ -3497,6 +3685,7 @@ export class ProxySessionController {
3497
3685
  await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, nextEffort);
3498
3686
  }
3499
3687
  this.store.updateRuntimeSession(loaded.record.localSessionId, { model, reasoningEffort: nextEffort });
3688
+ await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
3500
3689
  const effortMessage = nextEffort && nextEffort !== currentEffort
3501
3690
  ? `,思考强度已自动调整为 ${nextEffort}`
3502
3691
  : "";
@@ -3504,11 +3693,11 @@ export class ProxySessionController {
3504
3693
  await this.openExecutionSettings(contextKey, "model", {
3505
3694
  sessionId: loaded.record.localSessionId,
3506
3695
  updateMessageId: options.updateMessageId,
3507
- notice: `模型已切换为 ${cardCode(model)}${effortMessage},从下一次请求生效。`,
3696
+ notice: `模型已切换为 ${cardCode(model)}${effortMessage},并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
3508
3697
  });
3509
3698
  return;
3510
3699
  }
3511
- await this.outbound.sendText(contextKey, `模型已切换为 ${model}${effortMessage},从下一次请求生效。`);
3700
+ await this.outbound.sendText(contextKey, `模型已切换为 ${model}${effortMessage},并已保存为 ${loaded.record.agentName} Agent 的默认设置,从下一次请求生效。`);
3512
3701
  }
3513
3702
  async goal(contextKey, command) {
3514
3703
  const objective = command.action === "set" || command.action === "edit" ? command.objective : undefined;
@@ -3848,15 +4037,16 @@ export class ProxySessionController {
3848
4037
  }
3849
4038
  await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, effort);
3850
4039
  this.store.updateRuntimeSession(loaded.record.localSessionId, { reasoningEffort: effort });
4040
+ await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
3851
4041
  if (options.updateMessageId) {
3852
4042
  await this.openExecutionSettings(contextKey, "thinking", {
3853
4043
  sessionId: loaded.record.localSessionId,
3854
4044
  updateMessageId: options.updateMessageId,
3855
- notice: `思考强度已切换为 ${cardCode(effort)},从下一次请求生效。`,
4045
+ notice: `思考强度已切换为 ${cardCode(effort)},并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
3856
4046
  });
3857
4047
  return;
3858
4048
  }
3859
- await this.outbound.sendText(contextKey, `思考强度已切换为 ${effort},从下一次请求生效。`);
4049
+ await this.outbound.sendText(contextKey, `思考强度已切换为 ${effort},并已保存为 ${loaded.record.agentName} Agent 的默认设置,从下一次请求生效。`);
3860
4050
  }
3861
4051
  async permissions(contextKey, mode, options = {}) {
3862
4052
  if (!mode) {
@@ -3870,17 +4060,20 @@ export class ProxySessionController {
3870
4060
  await loaded.runtime.setPermissionMode(record.localSessionId, mode);
3871
4061
  loaded.session.permissionMode = mode;
3872
4062
  this.store.updateRuntimeSession(record.localSessionId, { permissionMode: mode });
4063
+ await this.persistAgentExecutionDefaults(record.localSessionId);
3873
4064
  if (options.updateMessageId) {
3874
4065
  await this.openExecutionSettings(contextKey, "permission", {
3875
4066
  sessionId: loaded.record.localSessionId,
3876
4067
  updateMessageId: options.updateMessageId,
3877
4068
  notice: mode === "auto"
3878
- ? "已切换为自动执行模式,从下一次请求生效。"
3879
- : "已切换为执行前确认模式,从下一次请求生效。",
4069
+ ? `已切换为自动执行模式,并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`
4070
+ : `已切换为执行前确认模式,并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
3880
4071
  });
3881
4072
  return;
3882
4073
  }
3883
- await this.outbound.sendText(contextKey, mode === "auto" ? "已切换为自动执行模式。" : "已切换为执行前确认模式。");
4074
+ await this.outbound.sendText(contextKey, mode === "auto"
4075
+ ? `已切换为自动执行模式,并已保存为 ${loaded.record.agentName} Agent 的默认设置。`
4076
+ : `已切换为执行前确认模式,并已保存为 ${loaded.record.agentName} Agent 的默认设置。`);
3884
4077
  }
3885
4078
  async openResetHistory(contextKey, options = {}) {
3886
4079
  const current = this.requireCurrentSession(contextKey);
@@ -3890,6 +4083,7 @@ export class ProxySessionController {
3890
4083
  if (!current.remoteSessionId || !this.isCodexSession(current)) {
3891
4084
  throw new Error("当前任务不是可 Reset 的 App Server 任务。");
3892
4085
  }
4086
+ await this.hydrateLegacyForkTurnHistory(contextKey, current);
3893
4087
  const completedTurns = this.store.listTaskTurnGraph(current.localSessionId);
3894
4088
  const completedTurnIds = new Set(completedTurns.map((turn) => turn.turnId));
3895
4089
  const runningTurnId = current.lastTurnStatus === "running" && current.lastTurnId
@@ -3936,6 +4130,7 @@ export class ProxySessionController {
3936
4130
  const page = Math.max(0, Math.min(Math.trunc(options.page ?? 0), totalPages - 1));
3937
4131
  const offset = page * RESET_HISTORY_PAGE_SIZE;
3938
4132
  const turns = allTurns.slice(offset, offset + RESET_HISTORY_PAGE_SIZE);
4133
+ const resettingTurnId = this.resetHistoryOperations.get(current.localSessionId);
3939
4134
  const entries = turns.flatMap((turn, index) => {
3940
4135
  const snapshot = turnViewSnapshot(turn.snapshot);
3941
4136
  if (!snapshot)
@@ -3944,17 +4139,17 @@ export class ProxySessionController {
3944
4139
  const summary = truncateText((snapshot.prompt ?? snapshot.taskTitle ?? "未记录对话内容").replace(/\s+/g, " ").trim(), 100);
3945
4140
  const isCurrent = current.lastTurnId === turn.turnId && current.lastTurnStatus === "completed";
3946
4141
  const isRunning = runningTurnId === turn.turnId;
4142
+ const isResetting = resettingTurnId === turn.turnId;
3947
4143
  return [{
3948
4144
  sequence: graph.sequence,
3949
4145
  graphNodeLine: graph.nodeLine,
3950
4146
  graphConnectorLine: graph.connectorLine,
3951
- lines: [
3952
- cardText(summary || "未记录对话内容"),
3953
- `${formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt)} · ${cardText(turn.turnId)}`,
3954
- ],
4147
+ lines: [cardText(summary || "未记录对话内容")],
4148
+ timestamp: formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt),
3955
4149
  current: isCurrent,
3956
4150
  running: isRunning,
3957
- actions: isCurrent || isRunning ? undefined : [{
4151
+ resetting: isResetting,
4152
+ actions: isCurrent || isRunning || resettingTurnId ? undefined : [{
3958
4153
  text: "Reset",
3959
4154
  value: {
3960
4155
  action: "turn_reset",
@@ -3993,6 +4188,7 @@ export class ProxySessionController {
3993
4188
  runningTurn
3994
4189
  ? `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个 turn(${completedTurns.length} 个已完成,1 个运行中)`
3995
4190
  : `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个已完成 turn`,
4191
+ ...(resettingTurnId ? ["正在 Reset 到所选轮次,请稍候…"] : []),
3996
4192
  ],
3997
4193
  pageActions,
3998
4194
  });
@@ -4995,6 +5191,21 @@ export class ProxySessionController {
4995
5191
  throw new Error(`未知 agent:${agentName}`);
4996
5192
  return agent;
4997
5193
  }
5194
+ async persistAgentExecutionDefaults(localSessionId) {
5195
+ const session = this.store.getSession(localSessionId);
5196
+ if (!session)
5197
+ throw new Error(`找不到任务:${localSessionId}`);
5198
+ const agent = this.ensureAgent(session.agentName);
5199
+ const observed = {
5200
+ ...(session.modelProvider?.trim() ? { modelProvider: session.modelProvider } : {}),
5201
+ ...(session.model?.trim() ? { model: session.model } : {}),
5202
+ ...(session.reasoningEffort?.trim() ? { reasoningEffort: session.reasoningEffort } : {}),
5203
+ ...(session.permissionMode ? { permissionMode: session.permissionMode } : {}),
5204
+ };
5205
+ const defaults = { ...(agent.defaults ?? {}), ...observed };
5206
+ await this.lifecycle?.persistAgentExecutionDefaults?.(session.agentName, defaults);
5207
+ agent.defaults = defaults;
5208
+ }
4998
5209
  agentLabel(agentName) {
4999
5210
  return this.ensureAgent(agentName).title;
5000
5211
  }
@@ -5056,6 +5267,14 @@ function threadForkAnchorMessageIds(message) {
5056
5267
  return [...new Set([message.rootMessageId, message.parentMessageId]
5057
5268
  .filter((messageId) => Boolean(messageId && messageId !== message.messageId)))];
5058
5269
  }
5270
+ function mergeExecutionSettings(defaults, overrides) {
5271
+ return {
5272
+ modelProvider: overrides.modelProvider ?? defaults.modelProvider,
5273
+ model: overrides.model ?? defaults.model,
5274
+ reasoningEffort: overrides.reasoningEffort ?? defaults.reasoningEffort,
5275
+ permissionMode: overrides.permissionMode ?? defaults.permissionMode,
5276
+ };
5277
+ }
5059
5278
  function sessionStatusLabel(status, activeTurnId) {
5060
5279
  if (activeTurnId || status === "running")
5061
5280
  return "执行中";
@@ -5490,6 +5709,12 @@ function mergeRemoteTaskStatus(record, remote) {
5490
5709
  function normalizeRemoteTimestamp(value) {
5491
5710
  return value >= 10_000_000_000 ? value : value * 1_000;
5492
5711
  }
5712
+ function parseIsoTimestamp(value) {
5713
+ if (!value)
5714
+ return undefined;
5715
+ const timestamp = Date.parse(value);
5716
+ return Number.isFinite(timestamp) ? timestamp : undefined;
5717
+ }
5493
5718
  function latestRemoteTimestamp(...values) {
5494
5719
  const timestamps = values
5495
5720
  .filter((value) => value !== undefined)