@downcity/plugins 1.0.66 → 1.0.72

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 (75) hide show
  1. package/bin/BuiltinPlugins.d.ts +0 -5
  2. package/bin/BuiltinPlugins.d.ts.map +1 -1
  3. package/bin/BuiltinPlugins.js +1 -5
  4. package/bin/BuiltinPlugins.js.map +1 -1
  5. package/bin/index.d.ts +0 -2
  6. package/bin/index.d.ts.map +1 -1
  7. package/bin/index.js +0 -1
  8. package/bin/index.js.map +1 -1
  9. package/bin/task/runtime/TaskRunnerRound.d.ts.map +1 -1
  10. package/bin/task/runtime/TaskRunnerRound.js +7 -2
  11. package/bin/task/runtime/TaskRunnerRound.js.map +1 -1
  12. package/bin/task/runtime/TaskRunnerSession.d.ts.map +1 -1
  13. package/bin/task/runtime/TaskRunnerSession.js +10 -3
  14. package/bin/task/runtime/TaskRunnerSession.js.map +1 -1
  15. package/package.json +4 -3
  16. package/scripts/unrestricted-sandbox-approval.test.mjs +110 -9
  17. package/src/BuiltinPlugins.ts +0 -9
  18. package/src/index.ts +0 -5
  19. package/src/task/runtime/TaskRunnerRound.ts +15 -5
  20. package/src/task/runtime/TaskRunnerSession.ts +11 -4
  21. package/bin/shell/Index.d.ts +0 -9
  22. package/bin/shell/Index.d.ts.map +0 -1
  23. package/bin/shell/Index.js +0 -9
  24. package/bin/shell/Index.js.map +0 -1
  25. package/bin/shell/ShellPlugin.d.ts +0 -65
  26. package/bin/shell/ShellPlugin.d.ts.map +0 -1
  27. package/bin/shell/ShellPlugin.js +0 -175
  28. package/bin/shell/ShellPlugin.js.map +0 -1
  29. package/bin/shell/ShellRuntimeTypes.d.ts +0 -160
  30. package/bin/shell/ShellRuntimeTypes.d.ts.map +0 -1
  31. package/bin/shell/ShellRuntimeTypes.js +0 -10
  32. package/bin/shell/ShellRuntimeTypes.js.map +0 -1
  33. package/bin/shell/runtime/Paths.d.ts +0 -12
  34. package/bin/shell/runtime/Paths.d.ts.map +0 -1
  35. package/bin/shell/runtime/Paths.js +0 -21
  36. package/bin/shell/runtime/Paths.js.map +0 -1
  37. package/bin/shell/runtime/ShellActionResponse.d.ts +0 -52
  38. package/bin/shell/runtime/ShellActionResponse.d.ts.map +0 -1
  39. package/bin/shell/runtime/ShellActionResponse.js +0 -73
  40. package/bin/shell/runtime/ShellActionResponse.js.map +0 -1
  41. package/bin/shell/runtime/ShellActionRuntime.d.ts +0 -73
  42. package/bin/shell/runtime/ShellActionRuntime.d.ts.map +0 -1
  43. package/bin/shell/runtime/ShellActionRuntime.js +0 -647
  44. package/bin/shell/runtime/ShellActionRuntime.js.map +0 -1
  45. package/bin/shell/runtime/ShellActionRuntimeSupport.d.ts +0 -88
  46. package/bin/shell/runtime/ShellActionRuntimeSupport.d.ts.map +0 -1
  47. package/bin/shell/runtime/ShellActionRuntimeSupport.js +0 -353
  48. package/bin/shell/runtime/ShellActionRuntimeSupport.js.map +0 -1
  49. package/bin/shell/runtime/ShellApprovalRuntime.d.ts +0 -62
  50. package/bin/shell/runtime/ShellApprovalRuntime.d.ts.map +0 -1
  51. package/bin/shell/runtime/ShellApprovalRuntime.js +0 -215
  52. package/bin/shell/runtime/ShellApprovalRuntime.js.map +0 -1
  53. package/bin/shell/runtime/ShellProcessEvents.d.ts +0 -22
  54. package/bin/shell/runtime/ShellProcessEvents.d.ts.map +0 -1
  55. package/bin/shell/runtime/ShellProcessEvents.js +0 -41
  56. package/bin/shell/runtime/ShellProcessEvents.js.map +0 -1
  57. package/bin/shell/runtime/ShellRuntimeEnvironment.d.ts +0 -21
  58. package/bin/shell/runtime/ShellRuntimeEnvironment.d.ts.map +0 -1
  59. package/bin/shell/runtime/ShellRuntimeEnvironment.js +0 -70
  60. package/bin/shell/runtime/ShellRuntimeEnvironment.js.map +0 -1
  61. package/bin/shell/types/ShellPluginOptions.d.ts +0 -103
  62. package/bin/shell/types/ShellPluginOptions.d.ts.map +0 -1
  63. package/bin/shell/types/ShellPluginOptions.js +0 -10
  64. package/bin/shell/types/ShellPluginOptions.js.map +0 -1
  65. package/src/shell/Index.ts +0 -9
  66. package/src/shell/ShellPlugin.ts +0 -239
  67. package/src/shell/ShellRuntimeTypes.ts +0 -168
  68. package/src/shell/runtime/Paths.ts +0 -28
  69. package/src/shell/runtime/ShellActionResponse.ts +0 -135
  70. package/src/shell/runtime/ShellActionRuntime.ts +0 -794
  71. package/src/shell/runtime/ShellActionRuntimeSupport.ts +0 -477
  72. package/src/shell/runtime/ShellApprovalRuntime.ts +0 -276
  73. package/src/shell/runtime/ShellProcessEvents.ts +0 -65
  74. package/src/shell/runtime/ShellRuntimeEnvironment.ts +0 -71
  75. package/src/shell/types/ShellPluginOptions.ts +0 -122
@@ -1,477 +0,0 @@
1
- /**
2
- * Shell action 运行时辅助能力。
3
- *
4
- * 关键点(中文)
5
- * - 统一承载 ShellActionRuntime 的内部共享逻辑:环境组装、持久化、waiter 协调、session 查找。
6
- * - 对外暴露给 ShellActionRuntime 的只有纯运行时辅助函数,不直接承担 plugin action 编排。
7
- */
8
-
9
- import path from "node:path";
10
- import fs from "fs-extra";
11
- import type { AgentContext } from "@downcity/agent/internal/types/runtime/agent/AgentContext.js";
12
- import type {
13
- ShellPluginState,
14
- ShellSessionRuntimeState,
15
- } from "@/shell/ShellRuntimeTypes.js";
16
- import type {
17
- ResolvedShellPluginOptions,
18
- ShellPluginOptions,
19
- } from "@/shell/types/ShellPluginOptions.js";
20
- import type {
21
- ShellQueryRequest,
22
- ShellSessionSnapshot,
23
- ShellSessionStatus,
24
- } from "@downcity/agent/internal/executor/tools/shell/types/ShellPlugin.js";
25
- import { getShellOutputPath, getShellSnapshotPath } from "./Paths.js";
26
- import { readChatMetaBySessionId } from "@/chat/runtime/ChatMetaStore.js";
27
- import { resolveChatQueueStore } from "@/chat/runtime/ChatQueueStore.js";
28
- import { resolveOwnerContextId } from "./ShellRuntimeEnvironment.js";
29
- export {
30
- buildShellEnv,
31
- resolveOwnerContextId,
32
- resolveShellCwd,
33
- } from "./ShellRuntimeEnvironment.js";
34
- export {
35
- buildActionResponse,
36
- createOutputChunk,
37
- } from "./ShellActionResponse.js";
38
-
39
- const DEFAULT_SHELL_PLUGIN_OPTIONS: ResolvedShellPluginOptions = {
40
- maxActiveShells: 64,
41
- cleanupDelayMs: 10 * 60 * 1000,
42
- maxInMemoryOutputChars: 1_000_000,
43
- outputPreviewChars: 280,
44
- minWaitMs: 50,
45
- maxWaitMs: 30_000,
46
- defaultInlineWaitMs: 1_200,
47
- defaultWaitTimeoutMs: 10_000,
48
- defaultExecTimeoutMs: 60_000,
49
- defaultApprovalTimeoutMs: 120_000,
50
- };
51
-
52
- /**
53
- * shell.start 默认内联等待时间。
54
- */
55
- export const DEFAULT_INLINE_WAIT_MS = DEFAULT_SHELL_PLUGIN_OPTIONS.defaultInlineWaitMs;
56
-
57
- /**
58
- * shell.wait 默认等待超时。
59
- */
60
- export const DEFAULT_WAIT_TIMEOUT_MS = DEFAULT_SHELL_PLUGIN_OPTIONS.defaultWaitTimeoutMs;
61
-
62
- /**
63
- * shell.exec 默认总超时。
64
- */
65
- export const DEFAULT_EXEC_TIMEOUT_MS = DEFAULT_SHELL_PLUGIN_OPTIONS.defaultExecTimeoutMs;
66
-
67
- function readPositiveInteger(
68
- value: number | undefined,
69
- fallback: number,
70
- ): number {
71
- if (typeof value !== "number" || !Number.isFinite(value)) {
72
- return fallback;
73
- }
74
- return Math.max(1, Math.floor(value));
75
- }
76
-
77
- /**
78
- * 归一化 ShellPlugin 可选运行参数。
79
- */
80
- export function resolveShellPluginOptions(
81
- options: ShellPluginOptions = {},
82
- ): ResolvedShellPluginOptions {
83
- const minWaitMs = readPositiveInteger(
84
- options.minWaitMs,
85
- DEFAULT_SHELL_PLUGIN_OPTIONS.minWaitMs,
86
- );
87
- const maxWaitMs = Math.max(
88
- minWaitMs,
89
- readPositiveInteger(
90
- options.maxWaitMs,
91
- DEFAULT_SHELL_PLUGIN_OPTIONS.maxWaitMs,
92
- ),
93
- );
94
- return {
95
- maxActiveShells: readPositiveInteger(
96
- options.maxActiveShells,
97
- DEFAULT_SHELL_PLUGIN_OPTIONS.maxActiveShells,
98
- ),
99
- cleanupDelayMs: readPositiveInteger(
100
- options.cleanupDelayMs,
101
- DEFAULT_SHELL_PLUGIN_OPTIONS.cleanupDelayMs,
102
- ),
103
- maxInMemoryOutputChars: readPositiveInteger(
104
- options.maxInMemoryOutputChars,
105
- DEFAULT_SHELL_PLUGIN_OPTIONS.maxInMemoryOutputChars,
106
- ),
107
- outputPreviewChars: readPositiveInteger(
108
- options.outputPreviewChars,
109
- DEFAULT_SHELL_PLUGIN_OPTIONS.outputPreviewChars,
110
- ),
111
- minWaitMs,
112
- maxWaitMs,
113
- defaultInlineWaitMs: readPositiveInteger(
114
- options.defaultInlineWaitMs,
115
- DEFAULT_SHELL_PLUGIN_OPTIONS.defaultInlineWaitMs,
116
- ),
117
- defaultWaitTimeoutMs: readPositiveInteger(
118
- options.defaultWaitTimeoutMs,
119
- DEFAULT_SHELL_PLUGIN_OPTIONS.defaultWaitTimeoutMs,
120
- ),
121
- defaultExecTimeoutMs: readPositiveInteger(
122
- options.defaultExecTimeoutMs,
123
- DEFAULT_SHELL_PLUGIN_OPTIONS.defaultExecTimeoutMs,
124
- ),
125
- defaultApprovalTimeoutMs: readPositiveInteger(
126
- options.defaultApprovalTimeoutMs,
127
- DEFAULT_SHELL_PLUGIN_OPTIONS.defaultApprovalTimeoutMs,
128
- ),
129
- };
130
- }
131
-
132
- /**
133
- * 创建 shell plugin runtime 初始状态。
134
- */
135
- export function createShellPluginState(
136
- options: ShellPluginOptions = {},
137
- ): ShellPluginState {
138
- return {
139
- options: resolveShellPluginOptions(options),
140
- sessions: new Map<string, ShellSessionRuntimeState>(),
141
- approvals: new Map(),
142
- context: null,
143
- };
144
- }
145
-
146
- /**
147
- * 返回当前毫秒时间戳。
148
- */
149
- export function nowMs(): number {
150
- return Date.now();
151
- }
152
-
153
- /**
154
- * 归一化 wait/timeout 参数。
155
- */
156
- export function clampWaitMs(value: number | undefined, fallback: number): number {
157
- return clampWaitMsWithOptions(DEFAULT_SHELL_PLUGIN_OPTIONS, value, fallback);
158
- }
159
-
160
- /**
161
- * 结合 ShellPlugin options 归一化 wait/timeout 参数。
162
- */
163
- export function clampWaitMsWithOptions(
164
- options: ResolvedShellPluginOptions,
165
- value: number | undefined,
166
- fallback: number,
167
- ): number {
168
- const raw =
169
- typeof value === "number" && Number.isFinite(value)
170
- ? Math.floor(value)
171
- : fallback;
172
- return Math.min(options.maxWaitMs, Math.max(options.minWaitMs, raw));
173
- }
174
-
175
- function normalizeOutputChunk(raw: string): string {
176
- if (!raw) return "";
177
- return raw
178
- .replace(/\r\n/g, "\n")
179
- .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "");
180
- }
181
-
182
- function deriveExitStatus(exitCode: number | undefined): ShellSessionStatus {
183
- if (exitCode === -9 || exitCode === 137) return "killed";
184
- if (typeof exitCode === "number" && exitCode === 0) return "completed";
185
- return "failed";
186
- }
187
-
188
- /**
189
- * 判断 shell 是否已进入终态。
190
- */
191
- export function isTerminalStatus(status: ShellSessionStatus): boolean {
192
- return (
193
- status === "completed" ||
194
- status === "failed" ||
195
- status === "killed" ||
196
- status === "expired"
197
- );
198
- }
199
-
200
- function extractExternalRefsFromText(
201
- text: string,
202
- current: ShellSessionSnapshot["externalRefs"],
203
- ): ShellSessionSnapshot["externalRefs"] {
204
- const next = [...current];
205
- const register = (kind: string, value: string, label?: string): void => {
206
- const normalized = String(value || "").trim();
207
- if (!normalized) return;
208
- if (next.some((item) => item.kind === kind && item.value === normalized)) return;
209
- next.push({ kind, value: normalized, ...(label ? { label } : {}) });
210
- };
211
-
212
- const threadIdRegex = /thread_id[:=]\s*([a-zA-Z0-9_-]{6,})/g;
213
- for (const match of text.matchAll(threadIdRegex)) {
214
- register("thread_id", String(match[1] || ""), "external thread id");
215
- }
216
- return next;
217
- }
218
-
219
- /**
220
- * 持久化 shell snapshot。
221
- */
222
- export async function persistSnapshot(session: ShellSessionRuntimeState): Promise<void> {
223
- await fs.ensureDir(path.dirname(session.snapshotFilePath));
224
- await fs.writeJson(session.snapshotFilePath, session.snapshot, { spaces: 2 });
225
- }
226
-
227
- function enqueuePersistedAppend(
228
- session: ShellSessionRuntimeState,
229
- text: string,
230
- ): Promise<void> {
231
- session.writeChain = session.writeChain.then(async () => {
232
- await fs.ensureDir(path.dirname(session.outputFilePath));
233
- await fs.appendFile(session.outputFilePath, text, "utf-8");
234
- });
235
- return session.writeChain;
236
- }
237
-
238
- function notifyWaiters(session: ShellSessionRuntimeState): void {
239
- for (const waiter of Array.from(session.waiters)) {
240
- clearTimeout(waiter.timer);
241
- session.waiters.delete(waiter);
242
- waiter.resolve();
243
- }
244
- }
245
-
246
- async function emitChatCompletionEvent(
247
- context: AgentContext,
248
- snapshot: ShellSessionSnapshot,
249
- ): Promise<void> {
250
- const ownerContextId = String(snapshot.ownerContextId || "").trim();
251
- if (!ownerContextId || snapshot.notificationSent !== false) return;
252
-
253
- const meta = await readChatMetaBySessionId({ context, sessionId: ownerContextId });
254
- if (!meta) return;
255
-
256
- const lines = [
257
- "[内部 shell 状态通知]",
258
- `shell_id: ${snapshot.shellId}`,
259
- `status: ${snapshot.status}`,
260
- `exit_code: ${typeof snapshot.exitCode === "number" ? snapshot.exitCode : "null"}`,
261
- `cmd: ${snapshot.cmd}`,
262
- ];
263
- if (snapshot.lastOutputPreview) {
264
- lines.push(`last_output_preview: ${snapshot.lastOutputPreview}`);
265
- }
266
- if (snapshot.externalRefs.length > 0) {
267
- const refs = snapshot.externalRefs.map((item) => `${item.kind}=${item.value}`);
268
- lines.push(`external_refs: ${refs.join(", ")}`);
269
- }
270
- lines.push("请根据当前 shell 的状态,主动向用户简洁汇报结果或最新进展。");
271
- const text = lines.join("\n");
272
-
273
- resolveChatQueueStore(context).enqueue({
274
- kind: "exec",
275
- channel: meta.channel,
276
- targetId: meta.chatId,
277
- sessionId: ownerContextId,
278
- text,
279
- ...(meta.targetType ? { targetType: meta.targetType } : {}),
280
- ...(typeof meta.threadId === "number" ? { threadId: meta.threadId } : {}),
281
- ...(meta.messageId ? { messageId: meta.messageId } : {}),
282
- ...(meta.actorId ? { actorId: meta.actorId } : {}),
283
- ...(meta.actorName ? { actorName: meta.actorName } : {}),
284
- extra: {
285
- note: "shell_session_auto_notify",
286
- internal: true,
287
- shellId: snapshot.shellId,
288
- shellStatus: snapshot.status,
289
- exitCode:
290
- typeof snapshot.exitCode === "number" ? snapshot.exitCode : null,
291
- },
292
- });
293
- }
294
-
295
- /**
296
- * 更新 session snapshot 并唤醒等待者。
297
- */
298
- export async function updateSessionSnapshot(
299
- session: ShellSessionRuntimeState,
300
- updater: (snapshot: ShellSessionSnapshot) => void | ShellSessionSnapshot,
301
- ): Promise<void> {
302
- const result = updater(session.snapshot);
303
- if (result) {
304
- session.snapshot = result;
305
- }
306
- session.snapshot.updatedAt = nowMs();
307
- session.snapshot.version += 1;
308
- await persistSnapshot(session);
309
- notifyWaiters(session);
310
- }
311
-
312
- /**
313
- * 追加 shell 输出并同步更新快照。
314
- */
315
- export async function appendSessionOutput(
316
- state: ShellPluginState,
317
- session: ShellSessionRuntimeState,
318
- raw: string,
319
- ): Promise<void> {
320
- const text = normalizeOutputChunk(raw);
321
- if (!text) return;
322
-
323
- session.outputText += text;
324
- if (session.outputText.length > state.options.maxInMemoryOutputChars) {
325
- const overflow = session.outputText.length - state.options.maxInMemoryOutputChars;
326
- session.outputText = session.outputText.slice(overflow);
327
- session.snapshot.droppedChars += overflow;
328
- }
329
-
330
- session.snapshot.outputChars += text.length;
331
- session.snapshot.lastOutputAt = nowMs();
332
- session.snapshot.lastOutputPreview = session.outputText
333
- .slice(-state.options.outputPreviewChars)
334
- .trim();
335
- session.snapshot.externalRefs = extractExternalRefsFromText(
336
- text,
337
- session.snapshot.externalRefs,
338
- );
339
- await enqueuePersistedAppend(session, text);
340
- await updateSessionSnapshot(session, () => undefined);
341
- }
342
-
343
- /**
344
- * 为终态 shell 安排延迟清理。
345
- */
346
- export function scheduleCleanup(state: ShellPluginState, shellId: string): void {
347
- const session = state.sessions.get(shellId);
348
- if (!session) return;
349
- if (session.cleanupTimer) clearTimeout(session.cleanupTimer);
350
- session.cleanupTimer = setTimeout(() => {
351
- const current = state.sessions.get(shellId);
352
- if (!current) return;
353
- state.sessions.delete(shellId);
354
- }, state.options.cleanupDelayMs);
355
- if (typeof session.cleanupTimer.unref === "function") {
356
- session.cleanupTimer.unref();
357
- }
358
- }
359
-
360
- /**
361
- * 控制 in-memory shell session 容量。
362
- */
363
- export function ensureCapacity(state: ShellPluginState): void {
364
- if (state.sessions.size < state.options.maxActiveShells) return;
365
- const removable = Array.from(state.sessions.values())
366
- .filter((item) => item.snapshot.status !== "running" && item.snapshot.status !== "starting")
367
- .sort((a, b) => a.snapshot.updatedAt - b.snapshot.updatedAt);
368
- for (const item of removable) {
369
- if (state.sessions.size < state.options.maxActiveShells) break;
370
- state.sessions.delete(item.snapshot.shellId);
371
- }
372
- if (state.sessions.size >= state.options.maxActiveShells) {
373
- throw new Error(
374
- `Too many active shell sessions (${state.sessions.size}). Please close or wait older sessions first.`,
375
- );
376
- }
377
- }
378
-
379
- async function loadPersistedSnapshot(
380
- context: AgentContext,
381
- shellId: string,
382
- ): Promise<ShellSessionSnapshot | null> {
383
- const file = getShellSnapshotPath(context.rootPath, shellId);
384
- if (!(await fs.pathExists(file))) return null;
385
- const raw = await fs.readJson(file).catch(() => null);
386
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
387
- const snapshot = raw as ShellSessionSnapshot;
388
- return typeof snapshot.shellId === "string" ? snapshot : null;
389
- }
390
-
391
- async function readPersistedOutput(
392
- context: AgentContext,
393
- shellId: string,
394
- ): Promise<string> {
395
- const file = getShellOutputPath(context.rootPath, shellId);
396
- if (!(await fs.pathExists(file))) return "";
397
- return await fs.readFile(file, "utf-8");
398
- }
399
-
400
- /**
401
- * 按 shellId 或 ownerContext 解析目标 session。
402
- */
403
- export async function resolveSession(
404
- state: ShellPluginState,
405
- context: AgentContext,
406
- query: ShellQueryRequest,
407
- ): Promise<ShellSessionRuntimeState | { snapshot: ShellSessionSnapshot; outputText: string } | null> {
408
- const explicitShellId = String(query.shellId || "").trim();
409
- if (explicitShellId) {
410
- const inMemory = state.sessions.get(explicitShellId);
411
- if (inMemory) return inMemory;
412
- const snapshot = await loadPersistedSnapshot(context, explicitShellId);
413
- if (!snapshot) return null;
414
- return {
415
- snapshot,
416
- outputText: await readPersistedOutput(context, explicitShellId),
417
- };
418
- }
419
-
420
- const ownerContextId = resolveOwnerContextId(query.ownerContextId);
421
- const cmd = String(query.cmd || "").trim().toLowerCase();
422
- if (!ownerContextId) return null;
423
- const includeCompleted = query.includeCompleted === true;
424
- const matched = Array.from(state.sessions.values())
425
- .filter((item) => {
426
- if (item.snapshot.ownerContextId !== ownerContextId) return false;
427
- if (!includeCompleted) {
428
- if (
429
- item.snapshot.status !== "running" &&
430
- item.snapshot.status !== "starting"
431
- ) {
432
- return false;
433
- }
434
- }
435
- if (!cmd) return true;
436
- return item.snapshot.cmd.toLowerCase().includes(cmd);
437
- })
438
- .sort((a, b) => b.snapshot.updatedAt - a.snapshot.updatedAt);
439
- return matched[0] || null;
440
- }
441
-
442
- /**
443
- * 判断解析出的 session 是否仍在内存中活动。
444
- */
445
- export function isInMemorySession(
446
- value: ShellSessionRuntimeState | { snapshot: ShellSessionSnapshot; outputText: string },
447
- ): value is ShellSessionRuntimeState {
448
- return "child" in value;
449
- }
450
-
451
- /**
452
- * 处理 shell 退出后的状态收口。
453
- */
454
- export async function finalizeExit(
455
- state: ShellPluginState,
456
- session: ShellSessionRuntimeState,
457
- exitCode: number,
458
- ): Promise<void> {
459
- await updateSessionSnapshot(session, (snapshot) => {
460
- snapshot.status = deriveExitStatus(exitCode);
461
- snapshot.exitCode = exitCode;
462
- snapshot.endedAt = nowMs();
463
- snapshot.pid = session.child.pid ?? snapshot.pid;
464
- });
465
- session.resolveCompletion();
466
- scheduleCleanup(state, session.snapshot.shellId);
467
-
468
- if (
469
- session.snapshot.autoNotifyOnExit &&
470
- session.snapshot.notificationSent === false &&
471
- state.context
472
- ) {
473
- await emitChatCompletionEvent(state.context, session.snapshot);
474
- session.snapshot.notificationSent = true;
475
- await persistSnapshot(session);
476
- }
477
- }