@co0ontty/wand 4.89.5 → 4.90.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 (197) hide show
  1. package/README.md +5 -3
  2. package/dist/agent-dispatch.js +4 -9
  3. package/dist/ai-team-employee-binding.js +10 -9
  4. package/dist/ai-team-runner.d.ts +2 -1
  5. package/dist/ai-team-runner.js +38 -2
  6. package/dist/ai-team-types.js +3 -2
  7. package/dist/ai-text-runner.d.ts +61 -0
  8. package/dist/ai-text-runner.js +653 -0
  9. package/dist/avatar-input.d.ts +2 -0
  10. package/dist/avatar-input.js +19 -0
  11. package/dist/bounded-process-output.d.ts +5 -0
  12. package/dist/bounded-process-output.js +9 -0
  13. package/dist/build-info.json +3 -3
  14. package/dist/command-error.d.ts +6 -0
  15. package/dist/command-error.js +8 -0
  16. package/dist/config.js +11 -42
  17. package/dist/conversation-service.d.ts +12 -2
  18. package/dist/conversation-service.js +104 -25
  19. package/dist/conversation-session-preview.d.ts +2 -0
  20. package/dist/conversation-session-preview.js +4 -0
  21. package/dist/decision-client.js +22 -6
  22. package/dist/decision-expert-employee.js +2 -2
  23. package/dist/decision-expert-identity.d.ts +1 -1
  24. package/dist/decision-expert-identity.js +1 -1
  25. package/dist/decision-expert-service.d.ts +1 -0
  26. package/dist/decision-expert-service.js +82 -76
  27. package/dist/decision-hardware.js +4 -3
  28. package/dist/decision-types.d.ts +4 -0
  29. package/dist/decision-types.js +4 -0
  30. package/dist/deployment-lock.d.ts +13 -0
  31. package/dist/deployment-lock.js +189 -0
  32. package/dist/employee-text.js +1 -1
  33. package/dist/fixed-employee-avatar.d.ts +7 -0
  34. package/dist/fixed-employee-avatar.js +13 -0
  35. package/dist/git-quick-commit.d.ts +5 -40
  36. package/dist/git-quick-commit.js +8 -636
  37. package/dist/hr-employee-identity.d.ts +5 -0
  38. package/dist/hr-employee-identity.js +5 -0
  39. package/dist/hr-employee.d.ts +5 -0
  40. package/dist/hr-employee.js +21 -0
  41. package/dist/middleware/path-safety.js +1 -1
  42. package/dist/milestone-scope.js +1 -1
  43. package/dist/model-auto-assign.d.ts +1 -1
  44. package/dist/model-auto-assign.js +1 -1
  45. package/dist/models.d.ts +4 -0
  46. package/dist/models.js +21 -1
  47. package/dist/npm-update-utils.js +5 -44
  48. package/dist/pi-auto-resources.js +6 -3
  49. package/dist/plush-avatar.d.ts +10 -1
  50. package/dist/plush-avatar.js +13 -1
  51. package/dist/plush-cat-appearance.d.ts +97 -0
  52. package/dist/plush-cat-appearance.js +118 -0
  53. package/dist/plush-cat-whiskers.d.ts +6 -0
  54. package/dist/plush-cat-whiskers.js +42 -0
  55. package/dist/process-manager.d.ts +22 -21
  56. package/dist/process-manager.js +49 -83
  57. package/dist/prompt-optimizer.d.ts +1 -1
  58. package/dist/prompt-optimizer.js +1 -1
  59. package/dist/render-host.js +1 -19
  60. package/dist/scheduled-task-service.d.ts +41 -0
  61. package/dist/scheduled-task-service.js +294 -0
  62. package/dist/scheduled-task-state.d.ts +11 -0
  63. package/dist/scheduled-task-state.js +65 -0
  64. package/dist/scheduled-task-time.d.ts +4 -0
  65. package/dist/scheduled-task-time.js +82 -0
  66. package/dist/scheduled-task-types.d.ts +71 -0
  67. package/dist/scheduled-task-types.js +1 -0
  68. package/dist/server-ai-team-routes.js +3 -17
  69. package/dist/server-employee-routes.d.ts +3 -0
  70. package/dist/server-employee-routes.js +20 -38
  71. package/dist/server-file-routes.js +44 -1
  72. package/dist/server-pi-recommendation-routes.js +13 -4
  73. package/dist/server-request.d.ts +2 -0
  74. package/dist/server-request.js +9 -0
  75. package/dist/server-resume-routes.js +1 -1
  76. package/dist/server-scheduled-task-routes.d.ts +4 -0
  77. package/dist/server-scheduled-task-routes.js +34 -0
  78. package/dist/server-session-routes.d.ts +5 -13
  79. package/dist/server-session-routes.js +35 -70
  80. package/dist/server-task-routes.d.ts +6 -21
  81. package/dist/server-task-routes.js +25 -120
  82. package/dist/server-team-dispatch-routes.js +12 -2
  83. package/dist/server-workspace-routes.d.ts +1 -1
  84. package/dist/server-workspace-routes.js +33 -78
  85. package/dist/server.js +55 -20
  86. package/dist/session-model.d.ts +15 -0
  87. package/dist/session-model.js +23 -0
  88. package/dist/session-registry.d.ts +5 -1
  89. package/dist/session-registry.js +20 -11
  90. package/dist/session-request.d.ts +9 -0
  91. package/dist/session-request.js +27 -0
  92. package/dist/session-topic.d.ts +1 -1
  93. package/dist/session-topic.js +1 -1
  94. package/dist/session-transport.d.ts +3 -1
  95. package/dist/session-transport.js +11 -5
  96. package/dist/silicon-employee-draft.d.ts +2 -2
  97. package/dist/silicon-employee-draft.js +3 -3
  98. package/dist/socket-probe.d.ts +2 -0
  99. package/dist/socket-probe.js +20 -0
  100. package/dist/storage-schedules.d.ts +20 -0
  101. package/dist/storage-schedules.js +78 -0
  102. package/dist/storage-schema.d.ts +3 -0
  103. package/dist/storage-schema.js +702 -0
  104. package/dist/storage.d.ts +42 -8
  105. package/dist/storage.js +182 -715
  106. package/dist/structured-claude-adapter.js +12 -0
  107. package/dist/structured-claude-protocol.d.ts +4 -0
  108. package/dist/structured-claude-protocol.js +18 -4
  109. package/dist/structured-codex-adapter.js +11 -0
  110. package/dist/structured-exec-pump.js +79 -38
  111. package/dist/structured-failure.d.ts +21 -0
  112. package/dist/structured-failure.js +14 -0
  113. package/dist/structured-gemini-adapter.js +12 -2
  114. package/dist/structured-grok-adapter.js +12 -2
  115. package/dist/structured-opencode-adapter.js +8 -1
  116. package/dist/structured-qoder-adapter.js +14 -0
  117. package/dist/structured-session-manager.d.ts +13 -18
  118. package/dist/structured-session-manager.js +147 -517
  119. package/dist/system-employee.d.ts +1 -1
  120. package/dist/system-employee.js +1 -1
  121. package/dist/task-archive-policy.js +1 -1
  122. package/dist/task-read-model.d.ts +27 -0
  123. package/dist/task-read-model.js +47 -0
  124. package/dist/task-retention.d.ts +1 -1
  125. package/dist/task-retention.js +1 -1
  126. package/dist/task-title-service.d.ts +30 -0
  127. package/dist/task-title-service.js +122 -0
  128. package/dist/task-title.d.ts +1 -1
  129. package/dist/task-title.js +1 -1
  130. package/dist/terminal-daemon-client.js +1 -18
  131. package/dist/user-memory.js +1 -1
  132. package/dist/web-ui/asset-runtime.d.ts +12 -0
  133. package/dist/web-ui/asset-runtime.js +6 -0
  134. package/dist/web-ui/composer-avatar-activity.d.ts +6 -0
  135. package/dist/web-ui/composer-avatar-activity.js +29 -0
  136. package/dist/web-ui/content/ai-teams.js +13 -13
  137. package/dist/web-ui/content/module-manifest.json +1 -0
  138. package/dist/web-ui/content/modules/5478be410b112154/chat-render-PY6DIXNZ.js +24 -0
  139. package/dist/web-ui/content/modules/5478be410b112154/chunk-2YZLCGYN.js +1 -0
  140. package/dist/web-ui/content/modules/5478be410b112154/chunk-6CFHW3FA.js +111 -0
  141. package/dist/web-ui/content/modules/5478be410b112154/chunk-6XQEBIEX.js +1 -0
  142. package/dist/web-ui/content/modules/5478be410b112154/chunk-DDSO7OX7.js +1 -0
  143. package/dist/web-ui/content/modules/5478be410b112154/chunk-G3CXNMA2.js +20 -0
  144. package/dist/web-ui/content/modules/5478be410b112154/chunk-LGVAPHRS.js +36 -0
  145. package/dist/web-ui/content/modules/5478be410b112154/chunk-NURCNMRU.js +1 -0
  146. package/dist/web-ui/content/modules/5478be410b112154/chunk-PCMFGZKX.js +1 -0
  147. package/dist/web-ui/content/modules/5478be410b112154/chunk-QRNA4D7E.js +1 -0
  148. package/dist/web-ui/content/modules/5478be410b112154/chunk-TMRPHNJO.js +1 -0
  149. package/dist/web-ui/content/modules/5478be410b112154/chunk-WC7RK7LP.js +1 -0
  150. package/dist/web-ui/content/modules/5478be410b112154/chunk-YOXXNXOX.js +2 -0
  151. package/dist/web-ui/content/modules/5478be410b112154/chunk-YX475LXO.js +1 -0
  152. package/dist/web-ui/content/modules/5478be410b112154/employee-card-MDWKU4V3.js +1 -0
  153. package/dist/web-ui/content/modules/5478be410b112154/host-56WPSON2.js +1 -0
  154. package/dist/web-ui/content/modules/5478be410b112154/host-5QOHKHNC.js +1 -0
  155. package/dist/web-ui/content/modules/5478be410b112154/host-AWIJ5L2K.js +1 -0
  156. package/dist/web-ui/content/modules/5478be410b112154/host-BOZLGIT5.js +69 -0
  157. package/dist/web-ui/content/modules/5478be410b112154/host-EZV22Q7Y.js +4 -0
  158. package/dist/web-ui/content/modules/5478be410b112154/host-IW43Z5ZF.js +1 -0
  159. package/dist/web-ui/content/modules/5478be410b112154/host-JT7NIUQL.js +1 -0
  160. package/dist/web-ui/content/modules/5478be410b112154/host-JWEIDB55.js +3 -0
  161. package/dist/web-ui/content/modules/5478be410b112154/host-OU5TRYH6.js +2 -0
  162. package/dist/web-ui/content/modules/5478be410b112154/host-PRSTCPVE.js +5 -0
  163. package/dist/web-ui/content/modules/5478be410b112154/host-TWPJI3HE.js +1 -0
  164. package/dist/web-ui/content/modules/5478be410b112154/host-WZGJSNML.js +1 -0
  165. package/dist/web-ui/content/modules/5478be410b112154/host-X6KQVFD6.js +1 -0
  166. package/dist/web-ui/content/modules/5478be410b112154/host-YKCQFMQR.js +1 -0
  167. package/dist/web-ui/content/modules/5478be410b112154/host-bridge-2LZX6UUZ.js +1 -0
  168. package/dist/web-ui/content/modules/5478be410b112154/library-bridge-MQFJGTGY.js +4 -0
  169. package/dist/web-ui/content/modules/5478be410b112154/page-T3VUCU2W.js +3 -0
  170. package/dist/web-ui/content/modules/5478be410b112154/panel-HJBGBBPY.js +1 -0
  171. package/dist/web-ui/content/modules/5478be410b112154/runtime.js +920 -0
  172. package/dist/web-ui/content/modules/5478be410b112154/sha256-ANXV72UC.js +1 -0
  173. package/dist/web-ui/content/modules/5478be410b112154/task-board-host-AVMDIHIV.js +2 -0
  174. package/dist/web-ui/content/modules/5478be410b112154/workspace-agent-picker-KI7QVSK3.js +1 -0
  175. package/dist/web-ui/content/modules/5478be410b112154/workspace-window-L542FGP2.js +1 -0
  176. package/dist/web-ui/content/plush-avatar.js +177 -177
  177. package/dist/web-ui/content/scripts.js +1 -994
  178. package/dist/web-ui/content/styles.css +1 -1
  179. package/dist/web-ui/content/tailwind.css +1 -1
  180. package/dist/web-ui/embedded-assets.d.ts +32 -0
  181. package/dist/web-ui/embedded-assets.js +10 -8
  182. package/dist/web-ui/markdown.d.ts +3 -2
  183. package/dist/web-ui/markdown.js +61 -48
  184. package/dist/web-ui/message-file-target.d.ts +13 -0
  185. package/dist/web-ui/message-file-target.js +85 -0
  186. package/dist/web-ui/provider-identity.d.ts +1 -1
  187. package/dist/web-ui/provider-identity.js +3 -4
  188. package/dist/web-ui/react/local-preview/controller.d.ts +2 -0
  189. package/dist/web-ui/react/local-preview/controller.js +12 -0
  190. package/dist/web-ui/scripts.d.ts +10 -0
  191. package/dist/web-ui/scripts.js +42 -5
  192. package/package.json +6 -3
  193. package/browser-extension/icons/wand-128.png +0 -0
  194. package/browser-extension/icons/wand-16.png +0 -0
  195. package/browser-extension/icons/wand-32.png +0 -0
  196. package/browser-extension/icons/wand-48.png +0 -0
  197. package/dist/web-ui/content/licenses/codex-LICENSE.txt +0 -21
@@ -45,10 +45,29 @@ export interface ProcessManagerOptions {
45
45
  /** 本地决策入口;只有带首条提示词创建终端会话时用来结算「智能分配」。 */
46
46
  resolveDecisionEvaluate?: () => ((value: unknown, caller: string, signal?: AbortSignal) => Promise<DecisionResult>) | undefined;
47
47
  }
48
+ export interface ProcessStartOptions extends Pick<SessionSnapshot, "employeeId" | "employeeName" | "employeeAvatar"> {
49
+ resumedFromSessionId?: string;
50
+ autoRecovered?: boolean;
51
+ worktreeEnabled?: boolean;
52
+ provider?: SessionProvider;
53
+ model?: string;
54
+ reuseId?: string;
55
+ cols?: number;
56
+ rows?: number;
57
+ thinkingEffort?: SessionSnapshot["thinkingEffort"];
58
+ sessionSource?: SessionSource;
59
+ automationId?: string;
60
+ workspaceId?: string;
61
+ workspaceTaskId?: string;
62
+ interactiveShell?: boolean;
63
+ systemPrompt?: string;
64
+ }
48
65
  export declare class ProcessManager extends EventEmitter {
49
66
  private readonly config;
50
67
  private readonly storage;
51
68
  private readonly sessions;
69
+ /** Own the full start handshake, including model resolution and late host replies. */
70
+ private readonly startingSessions;
52
71
  private readonly logger;
53
72
  private readonly providerHistory;
54
73
  /** Quiet window for the non-Claude PTY turn tracker. */
@@ -59,8 +78,6 @@ export declare class ProcessManager extends EventEmitter {
59
78
  private readonly probePtyForegrounds;
60
79
  private readonly free;
61
80
  private readonly resolveDecisionEvaluate;
62
- /** 24h archive scan timer */
63
- private archiveTimer;
64
81
  /** 前台进程组采样定时器(见 samplePtyForegrounds) */
65
82
  private ptyForegroundTimer;
66
83
  /** 采样是 async 的,避免慢 ps 重叠成多个进程 */
@@ -129,24 +146,8 @@ export declare class ProcessManager extends EventEmitter {
129
146
  /** Stop all live work and flush pending state before storage is closed. */
130
147
  dispose(): void;
131
148
  private emitEvent;
132
- private cleanupOldSessions;
133
- start(command: string, cwd: string | undefined, mode: ExecutionMode, initialInput?: string, opts?: {
134
- resumedFromSessionId?: string;
135
- autoRecovered?: boolean;
136
- worktreeEnabled?: boolean;
137
- provider?: SessionProvider;
138
- model?: string;
139
- reuseId?: string;
140
- cols?: number;
141
- rows?: number;
142
- thinkingEffort?: SessionSnapshot["thinkingEffort"];
143
- sessionSource?: SessionSource;
144
- automationId?: string;
145
- workspaceId?: string;
146
- workspaceTaskId?: string;
147
- interactiveShell?: boolean;
148
- systemPrompt?: string;
149
- } & Pick<SessionSnapshot, "employeeId" | "employeeName" | "employeeAvatar">): Promise<SessionSnapshot>;
149
+ start(command: string, cwd: string | undefined, mode: ExecutionMode, initialInput?: string, opts?: ProcessStartOptions): Promise<SessionSnapshot>;
150
+ private startSession;
150
151
  list(): SessionSnapshot[];
151
152
  /** Return lightweight snapshots for the session list (no output/messages). */
152
153
  listSlim(): SessionSnapshot[];
@@ -172,6 +173,7 @@ export declare class ProcessManager extends EventEmitter {
172
173
  get(id: string): SessionSnapshot | null;
173
174
  /** Return only a session owned by this manager, without the SQLite fallback used by get(). */
174
175
  getOwned(id: string): SessionSnapshot | null;
176
+ getOwnedSlim(id: string): SessionSnapshot | null;
175
177
  getPtyTranscript(id: string): string | null;
176
178
  /**
177
179
  * Set the Claude model for an existing PTY session. Persists the selection
@@ -256,7 +258,6 @@ export declare class ProcessManager extends EventEmitter {
256
258
  * Use this at critical points (exit, stop, delete) to ensure no data loss.
257
259
  */
258
260
  private flushPersist;
259
- private archiveExpiredSessions;
260
261
  private assertCommandAllowed;
261
262
  /**
262
263
  * @deprecated Only retained for non-Claude-CLI sessions without ptyBridge.
@@ -27,7 +27,6 @@ import { describePtySpawnFailure } from "./ensure-node-pty-helper.js";
27
27
  import { resolveSessionProvider, resolveSystemAiContext } from "./session-ai-context.js";
28
28
  import { resolveSessionCwd } from "./session-cwd.js";
29
29
  import { inferProviderFromCommand, isSessionProvider, providerCliCommand } from "./session-provider.js";
30
- import { RETENTION_IDLE_MS } from "./retention.js";
31
30
  import { PtyTerminalState } from "./pty-terminal-state.js";
32
31
  import { isPtySubmitInput } from "./pty-turn-activity.js";
33
32
  import { samplePtyForegrounds } from "./pty-foreground.js";
@@ -486,9 +485,6 @@ function restoreTerminalState(state) {
486
485
  }
487
486
  return terminal;
488
487
  }
489
- const MAX_SESSIONS = 200;
490
- // 会话空闲 7 天后在这里自动归档,再由保留期扫描于 7 天后清理。任务窗口在设置里单独配置。
491
- const ARCHIVE_AFTER_MS = RETENTION_IDLE_MS;
492
488
  const CONFIRM_WINDOW_SIZE = 800;
493
489
  /**
494
490
  * Quiet window for the non-Claude PTY turn tracker: a submitted turn stays busy
@@ -555,6 +551,8 @@ export class ProcessManager extends EventEmitter {
555
551
  config;
556
552
  storage;
557
553
  sessions = new Map();
554
+ /** Own the full start handshake, including model resolution and late host replies. */
555
+ startingSessions = new Map();
558
556
  logger;
559
557
  providerHistory = new ProviderHistoryScanner();
560
558
  /** Quiet window for the non-Claude PTY turn tracker. */
@@ -565,8 +563,6 @@ export class ProcessManager extends EventEmitter {
565
563
  probePtyForegrounds;
566
564
  free;
567
565
  resolveDecisionEvaluate;
568
- /** 24h archive scan timer */
569
- archiveTimer = null;
570
566
  /** 前台进程组采样定时器(见 samplePtyForegrounds) */
571
567
  ptyForegroundTimer = null;
572
568
  /** 采样是 async 的,避免慢 ps 重叠成多个进程 */
@@ -698,16 +694,6 @@ export class ProcessManager extends EventEmitter {
698
694
  this.orphanRecoveredCount += 1;
699
695
  }
700
696
  }
701
- this.archiveExpiredSessions();
702
- this.archiveTimer = setInterval(() => {
703
- try {
704
- this.archiveExpiredSessions();
705
- }
706
- catch (err) {
707
- console.error(`[ProcessManager] archive scan failed: ${String(err)}`);
708
- }
709
- }, 60 * 1000);
710
- this.archiveTimer.unref?.();
711
697
  this.ptyForegroundTimer = setInterval(() => { void this.samplePtyForegrounds(); }, this.ptyForegroundSampleMs);
712
698
  this.ptyForegroundTimer.unref?.();
713
699
  }
@@ -1113,10 +1099,6 @@ export class ProcessManager extends EventEmitter {
1113
1099
  this.disposed = true;
1114
1100
  // 静默心跳不跨服务关闭存活;每个会话的 timer 也在 cleanupRecord 里单独撤销。
1115
1101
  this.ptyHeartbeat.dispose();
1116
- if (this.archiveTimer) {
1117
- clearInterval(this.archiveTimer);
1118
- this.archiveTimer = null;
1119
- }
1120
1102
  if (this.ptyForegroundTimer) {
1121
1103
  clearInterval(this.ptyForegroundTimer);
1122
1104
  this.ptyForegroundTimer = null;
@@ -1157,54 +1139,24 @@ export class ProcessManager extends EventEmitter {
1157
1139
  return;
1158
1140
  this.emit("process", event);
1159
1141
  }
1160
- cleanupOldSessions() {
1161
- // Only clean up when well over the limit
1162
- if (this.sessions.size < MAX_SESSIONS)
1163
- return;
1164
- const now = Date.now();
1165
- const STALE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
1166
- const removable = [];
1167
- for (const [id, record] of this.sessions) {
1168
- // Only remove archived, non-running sessions older than 7 days
1169
- if (record.status === "running")
1170
- continue;
1171
- if (!record.archived)
1172
- continue;
1173
- const ref = record.endedAt ?? record.startedAt;
1174
- const refMs = Date.parse(ref);
1175
- if (Number.isFinite(refMs) && now - refMs > STALE_MS) {
1176
- removable.push(id);
1177
- }
1178
- }
1179
- // Sort oldest first and remove enough to get back under the limit
1180
- const toRemove = removable
1181
- .sort((a, b) => {
1182
- const ra = this.sessions.get(a);
1183
- const rb = this.sessions.get(b);
1184
- return (ra?.endedAt || "").localeCompare(rb?.endedAt || "");
1185
- })
1186
- .slice(0, this.sessions.size - MAX_SESSIONS + 1);
1187
- for (const id of toRemove) {
1188
- const record = this.sessions.get(id);
1189
- if (record) {
1190
- this.logger.deleteSession(id);
1191
- if (record.provider === "claude") {
1192
- this.deleteClaudeCache(record);
1193
- }
1194
- }
1195
- this.sessions.delete(id);
1196
- this.terminalHost.forget(id);
1197
- this.lastPersistedMessageState.delete(id);
1198
- this.dirtySessions.delete(id);
1199
- this.storage.deleteSession(id);
1200
- }
1201
- if (toRemove.length > 0) {
1202
- this.providerHistory.invalidate();
1203
- }
1204
- }
1205
1142
  async start(command, cwd, mode, initialInput, opts) {
1206
1143
  if (this.disposed)
1207
1144
  throw new Error("ProcessManager has been disposed.");
1145
+ const id = opts?.reuseId || randomUUID();
1146
+ // Host operations are keyed by session id, not by the manager record. A
1147
+ // second handshake must not adopt/forget a cancelled first incarnation.
1148
+ if (this.startingSessions.has(id))
1149
+ throw new Error("终端启动仍在进行,请等待本次启动结束后再恢复。");
1150
+ const start = { cancelled: false };
1151
+ this.startingSessions.set(id, start);
1152
+ try {
1153
+ return await this.startSession(id, start, command, cwd, mode, initialInput, opts);
1154
+ }
1155
+ finally {
1156
+ this.startingSessions.delete(id);
1157
+ }
1158
+ }
1159
+ async startSession(id, start, command, cwd, mode, initialInput, opts) {
1208
1160
  if (isOpenRouterFreeSelector(opts?.model))
1209
1161
  throw new Error("免费分组仅支持 Pi 结构化对话。");
1210
1162
  if (!opts?.interactiveShell)
@@ -1252,7 +1204,8 @@ export class ProcessManager extends EventEmitter {
1252
1204
  if (preferredModels.some(isOpenRouterFreeSelector))
1253
1205
  throw new Error("免费模型分组仅支持 Pi 结构化对话。");
1254
1206
  const baseCwd = resolveSessionCwd(cwd, this.config.defaultCwd);
1255
- const id = opts?.reuseId || randomUUID();
1207
+ if (this.disposed || start.cancelled)
1208
+ throw new Error("终端启动已取消。");
1256
1209
  // When a session is being resumed under the same id, capture its prior
1257
1210
  // structured messages so the new bridge can present them as the chat
1258
1211
  // history. We deliberately do NOT carry over rawOutput — `claude --resume`
@@ -1407,7 +1360,6 @@ export class ProcessManager extends EventEmitter {
1407
1360
  if (initialClaudeSessionId && (provider === "claude" || provider === "codex")) {
1408
1361
  this.providerHistory.invalidate(provider);
1409
1362
  }
1410
- this.cleanupOldSessions();
1411
1363
  let child;
1412
1364
  try {
1413
1365
  const attached = await this.terminalHost.createOrAttach({
@@ -1434,6 +1386,20 @@ export class ProcessManager extends EventEmitter {
1434
1386
  throw new Error("Terminal host did not return a running process");
1435
1387
  }
1436
1388
  child = attached.process;
1389
+ if (this.disposed || start.cancelled || this.sessions.get(id) !== record) {
1390
+ // Explicit stop/delete still owns cancellation. Ordinary server shutdown
1391
+ // only detaches persistent hosts: the next server must be able to adopt
1392
+ // this in-progress spawn from the durable provisional record.
1393
+ if (start.cancelled || !this.terminalHost.persistent) {
1394
+ try {
1395
+ child.kill();
1396
+ }
1397
+ catch { /* best-effort cancellation */ }
1398
+ }
1399
+ if (!this.disposed)
1400
+ this.terminalHost.forget(id);
1401
+ throw new Error("终端启动已取消。");
1402
+ }
1437
1403
  record.output = attached.state.output;
1438
1404
  record.storedOutput = attached.state.output;
1439
1405
  record.ptyOutputSeq = attached.state.seq;
@@ -1445,6 +1411,11 @@ export class ProcessManager extends EventEmitter {
1445
1411
  this.initializeClaudeBridge(record, attached.state.output);
1446
1412
  }
1447
1413
  catch (err) {
1414
+ // stop/delete/dispose already committed the authoritative state. Neither
1415
+ // a late success nor failure may remove or resurrect that state.
1416
+ if (this.disposed || start.cancelled || this.sessions.get(id) !== record) {
1417
+ throw new Error("终端启动已取消。");
1418
+ }
1448
1419
  console.error("[ProcessManager] terminal spawn failed", { sessionId: id, error: String(err) });
1449
1420
  this.cleanupRecord(record, false);
1450
1421
  this.sessions.delete(id);
@@ -1749,6 +1720,11 @@ export class ProcessManager extends EventEmitter {
1749
1720
  result.output = record.storedOutput;
1750
1721
  return result;
1751
1722
  }
1723
+ getOwnedSlim(id) {
1724
+ this.terminalHost.flush?.();
1725
+ const record = this.sessions.get(id);
1726
+ return record ? this.snapshotSlim(record) : null;
1727
+ }
1752
1728
  getPtyTranscript(id) {
1753
1729
  return this.logger.readPtyOutputTail(id);
1754
1730
  }
@@ -2004,6 +1980,9 @@ export class ProcessManager extends EventEmitter {
2004
1980
  }
2005
1981
  stop(id) {
2006
1982
  const record = this.mustGet(id);
1983
+ const start = this.startingSessions.get(id);
1984
+ if (start)
1985
+ start.cancelled = true;
2007
1986
  if (record.status !== "running") {
2008
1987
  return this.snapshot(record);
2009
1988
  }
@@ -2109,6 +2088,9 @@ export class ProcessManager extends EventEmitter {
2109
2088
  }
2110
2089
  delete(id) {
2111
2090
  const record = this.mustGet(id);
2091
+ const start = this.startingSessions.get(id);
2092
+ if (start)
2093
+ start.cancelled = true;
2112
2094
  // Always clear pending timers
2113
2095
  // 会话删除后迟到的回合回调要失效,静默心跳也不能再给不存在的会话发事件。
2114
2096
  this.ptyHeartbeat.cancel(id);
@@ -2563,22 +2545,6 @@ export class ProcessManager extends EventEmitter {
2563
2545
  messagesDirty: forceFullSave,
2564
2546
  });
2565
2547
  }
2566
- archiveExpiredSessions() {
2567
- const now = Date.now();
2568
- for (const record of this.sessions.values()) {
2569
- if (record.archived || record.status === "running") {
2570
- continue;
2571
- }
2572
- const referenceTime = record.endedAt ?? record.startedAt;
2573
- const endedAtMs = Date.parse(referenceTime);
2574
- if (!Number.isFinite(endedAtMs) || now - endedAtMs < ARCHIVE_AFTER_MS) {
2575
- continue;
2576
- }
2577
- record.archived = true;
2578
- record.archivedAt = new Date(now).toISOString();
2579
- this.persist(record);
2580
- }
2581
- }
2582
2548
  assertCommandAllowed(command) {
2583
2549
  if (!isCommandAllowedByPrefixes(command, this.config.allowedCommandPrefixes)) {
2584
2550
  throw new Error("Command is not allowed by current configuration.");
@@ -1,4 +1,4 @@
1
- import { type QuickCommitAiOptions } from "./git-quick-commit.js";
1
+ import { type QuickCommitAiOptions } from "./ai-text-runner.js";
2
2
  export declare class PromptOptimizeError extends Error {
3
3
  readonly code: string;
4
4
  constructor(message: string, code: string);
@@ -1,4 +1,4 @@
1
- import { callConfiguredAiText } from "./git-quick-commit.js";
1
+ import { callConfiguredAiText } from "./ai-text-runner.js";
2
2
  const MAX_INPUT_LENGTH = 8000;
3
3
  export class PromptOptimizeError extends Error {
4
4
  code;
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync, readFileSync, unlinkSync } from "node:fs";
3
- import net from "node:net";
3
+ import { probeUnixSocket as isSocketListening } from "./socket-probe.js";
4
4
  import process from "node:process";
5
5
  import { getErrorMessage } from "./error-utils.js";
6
6
  import { isExecutableFile, readRenderBinaryVersion, resolveRenderBinaryPath } from "./render-binary.js";
@@ -351,24 +351,6 @@ function reapUnreadyRender(pid) {
351
351
  const timer = setTimeout(() => signal("SIGKILL"), 500);
352
352
  timer.unref?.();
353
353
  }
354
- /** 对端是否真的在监听:区分「活着但拒绝 adopt」与「上一轮崩溃留下的孤儿 socket 文件」。 */ function isSocketListening(socketPath) {
355
- return new Promise((resolve) => {
356
- const socket = net.createConnection(socketPath);
357
- let settled = false;
358
- const finish = (alive) => {
359
- if (settled)
360
- return;
361
- settled = true;
362
- clearTimeout(timer);
363
- socket.destroy();
364
- resolve(alive);
365
- };
366
- socket.once("connect", () => finish(true));
367
- socket.once("error", () => finish(false));
368
- const timer = setTimeout(() => finish(false), 300);
369
- timer.unref?.();
370
- });
371
- }
372
354
  function readLiveRenderPid(pidPath) {
373
355
  let pid;
374
356
  try {
@@ -0,0 +1,41 @@
1
+ import type { ConversationService } from "./conversation-service.js";
2
+ import type { ScheduledTaskDetail, ScheduledTaskSummary } from "./scheduled-task-types.js";
3
+ import type { WandStorage } from "./storage.js";
4
+ import type { ProcessEvent, SessionSnapshot } from "./types.js";
5
+ export declare class ScheduledTaskConflictError extends Error {
6
+ }
7
+ export declare class ScheduledTaskNotFoundError extends Error {
8
+ }
9
+ /** Scheduling owns durable occurrences; employee execution and retries remain in the normal conversation protocol. */
10
+ export declare class ScheduledTaskService {
11
+ private readonly deps;
12
+ private timer;
13
+ private pendingTick;
14
+ private disposed;
15
+ private readonly liveAcceptedRuns;
16
+ constructor(deps: {
17
+ storage: WandStorage;
18
+ conversations: Pick<ConversationService, "send" | "receipt">;
19
+ getSession(id: string): SessionSnapshot | null;
20
+ defaultCwd?: () => string | undefined;
21
+ now?: () => Date;
22
+ });
23
+ private now;
24
+ private requireEmployee;
25
+ private requireWorkspaceCwd;
26
+ private executionDirectory;
27
+ private requireTask;
28
+ private runView;
29
+ private summary;
30
+ list(includeCancelled?: boolean): ScheduledTaskSummary[];
31
+ get(id: string): ScheduledTaskDetail;
32
+ create(value: unknown): ScheduledTaskSummary;
33
+ update(id: string, value: unknown): ScheduledTaskSummary;
34
+ private reconcile;
35
+ /** Capture the original turn's terminal fact before a user can start another turn in the same session. */
36
+ ingestSessionEvent(event: ProcessEvent): void;
37
+ tick(): Promise<void>;
38
+ private runTick;
39
+ start(intervalMs?: number): void;
40
+ dispose(): Promise<void>;
41
+ }