chatccc 0.2.190 → 0.2.192

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 (72) hide show
  1. package/agent-prompts/claude_specific.md +45 -45
  2. package/agent-prompts/codex_specific.md +2 -2
  3. package/agent-prompts/cursor_specific.md +13 -13
  4. package/config.sample.json +14 -14
  5. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  6. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  7. package/im-skills/feishu-skill/skill.md +3 -3
  8. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  9. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  10. package/im-skills/wechat-file-skill/skill.md +10 -10
  11. package/im-skills/wechat-image-skill/skill.md +10 -10
  12. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  13. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  14. package/im-skills/wechat-video-skill/skill.md +10 -10
  15. package/package.json +1 -1
  16. package/scripts/postinstall-sharp-check.mjs +58 -58
  17. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  18. package/src/__tests__/builtin-chat-session.test.ts +76 -0
  19. package/src/__tests__/builtin-config.test.ts +33 -33
  20. package/src/__tests__/builtin-context.test.ts +126 -0
  21. package/src/__tests__/builtin-sigint.test.ts +56 -0
  22. package/src/__tests__/card-plain-text.test.ts +5 -5
  23. package/src/__tests__/cardkit.test.ts +60 -60
  24. package/src/__tests__/cards.test.ts +77 -77
  25. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  26. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  27. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  28. package/src/__tests__/claude-adapter.test.ts +592 -592
  29. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  30. package/src/__tests__/config-reload.test.ts +4 -4
  31. package/src/__tests__/config-sample.test.ts +17 -17
  32. package/src/__tests__/feishu-api.test.ts +60 -60
  33. package/src/__tests__/feishu-platform.test.ts +22 -22
  34. package/src/__tests__/format-message.test.ts +47 -47
  35. package/src/__tests__/orchestrator.test.ts +120 -120
  36. package/src/__tests__/privacy.test.ts +198 -198
  37. package/src/__tests__/raw-stream-log.test.ts +106 -106
  38. package/src/__tests__/session.test.ts +17 -17
  39. package/src/__tests__/shared-prefix.test.ts +36 -36
  40. package/src/__tests__/stream-state.test.ts +42 -42
  41. package/src/__tests__/web-ui.test.ts +209 -209
  42. package/src/adapters/claude-adapter.ts +566 -566
  43. package/src/adapters/claude-session-meta-store.ts +120 -120
  44. package/src/adapters/codex-adapter.ts +30 -30
  45. package/src/adapters/cursor-adapter.ts +46 -46
  46. package/src/adapters/raw-stream-log.ts +124 -124
  47. package/src/adapters/resource-monitor.ts +140 -140
  48. package/src/agent-delegate-task-rpc.ts +153 -153
  49. package/src/agent-delegate-task.ts +81 -81
  50. package/src/agent-stop-stuck.ts +129 -129
  51. package/src/builtin/cli.ts +211 -189
  52. package/src/builtin/context.ts +225 -0
  53. package/src/builtin/index.ts +232 -170
  54. package/src/builtin/sigint.ts +50 -0
  55. package/src/cards.ts +137 -137
  56. package/src/chatgpt-subscription-rpc.ts +27 -27
  57. package/src/chatgpt-subscription.ts +299 -299
  58. package/src/chrome-devtools-guard.ts +318 -318
  59. package/src/codex-reset-actions.ts +184 -184
  60. package/src/config.ts +86 -86
  61. package/src/feishu-platform.ts +20 -20
  62. package/src/format-message.ts +293 -293
  63. package/src/litellm-proxy.ts +374 -374
  64. package/src/orchestrator.ts +143 -143
  65. package/src/privacy.ts +118 -118
  66. package/src/session-chat-binding.ts +6 -6
  67. package/src/session-name.ts +8 -8
  68. package/src/session.ts +98 -98
  69. package/src/shared-prefix.ts +29 -29
  70. package/src/sim-platform.ts +20 -20
  71. package/src/turn-cards.ts +117 -117
  72. package/src/web-ui.ts +205 -205
@@ -1,124 +1,124 @@
1
- import { createWriteStream, type Dirent } from "node:fs";
2
- import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
3
- import { once } from "node:events";
4
- import { join } from "node:path";
5
- import { createGzip } from "node:zlib";
6
-
7
- export interface RawStreamLogOptions {
8
- enabled: boolean;
9
- rootDir: string;
10
- tool: string;
11
- sessionId: string;
12
- label: string;
13
- maxBytesPerTurn: number;
14
- retentionDays: number;
15
- }
16
-
17
- export interface RawStreamLogHandle {
18
- filePath: string;
19
- writeLine(line: string): void;
20
- close(options: { keep: boolean }): Promise<void>;
21
- }
22
-
23
- export function sanitizeLogPathSegment(value: string): string {
24
- const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^[._]+|_+$/g, "");
25
- return safe || "unknown";
26
- }
27
-
28
- async function cleanupOldRawStreamLogs(rootDir: string, retentionDays: number): Promise<void> {
29
- if (!Number.isFinite(retentionDays) || retentionDays <= 0) return;
30
- const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
31
-
32
- const visit = async (dir: string): Promise<void> => {
33
- let entries: Dirent[];
34
- try {
35
- entries = await readdir(dir, { withFileTypes: true });
36
- } catch {
37
- return;
38
- }
39
-
40
- await Promise.all(entries.map(async (entry) => {
41
- const path = join(dir, entry.name);
42
- if (entry.isDirectory()) {
43
- await visit(path);
44
- try {
45
- await rm(path, { recursive: false });
46
- } catch {
47
- // Directory is not empty or already gone.
48
- }
49
- return;
50
- }
51
-
52
- if (!entry.isFile()) return;
53
- try {
54
- const info = await stat(path);
55
- if (info.mtimeMs < cutoff) await rm(path, { force: true });
56
- } catch {
57
- // Best-effort cleanup only.
58
- }
59
- }));
60
- };
61
-
62
- await visit(rootDir);
63
- }
64
-
65
- export async function createRawStreamLog(options: RawStreamLogOptions): Promise<RawStreamLogHandle | null> {
66
- if (!options.enabled) return null;
67
-
68
- const maxBytes = Math.max(0, Math.floor(options.maxBytesPerTurn));
69
- const tool = sanitizeLogPathSegment(options.tool);
70
- const session = sanitizeLogPathSegment(options.sessionId);
71
- const label = sanitizeLogPathSegment(options.label);
72
- const dir = join(options.rootDir, tool, session);
73
- await mkdir(dir, { recursive: true });
74
- void cleanupOldRawStreamLogs(join(options.rootDir, tool), options.retentionDays);
75
-
76
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
77
- const filePath = join(dir, `${timestamp}-${label}.jsonl.gz`);
78
- const output = createWriteStream(filePath);
79
- const gzip = createGzip();
80
- gzip.pipe(output);
81
-
82
- let bytes = 0;
83
- let truncated = false;
84
- let ended = false;
85
-
86
- const writeLine = (line: string): void => {
87
- if (ended || truncated) return;
88
- const payload = `${line}\n`;
89
- const payloadBytes = Buffer.byteLength(payload, "utf-8");
90
- if (maxBytes > 0 && bytes + payloadBytes > maxBytes) {
91
- const marker = JSON.stringify({
92
- type: "chatccc_raw_stream_log_truncated",
93
- reason: "max_bytes_per_turn_exceeded",
94
- maxBytesPerTurn: maxBytes,
95
- writtenBytes: bytes,
96
- });
97
- gzip.write(`${marker}\n`);
98
- truncated = true;
99
- return;
100
- }
101
- bytes += payloadBytes;
102
- gzip.write(payload);
103
- };
104
-
105
- const close = async ({ keep }: { keep: boolean }): Promise<void> => {
106
- if (ended) return;
107
- ended = true;
108
- gzip.end();
109
- try {
110
- await once(output, "finish");
111
- } catch {
112
- // Ignore close errors; caller cannot recover from debug log failures.
113
- }
114
- if (!keep) {
115
- try {
116
- await unlink(filePath);
117
- } catch {
118
- // Best-effort cleanup only.
119
- }
120
- }
121
- };
122
-
123
- return { filePath, writeLine, close };
124
- }
1
+ import { createWriteStream, type Dirent } from "node:fs";
2
+ import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
3
+ import { once } from "node:events";
4
+ import { join } from "node:path";
5
+ import { createGzip } from "node:zlib";
6
+
7
+ export interface RawStreamLogOptions {
8
+ enabled: boolean;
9
+ rootDir: string;
10
+ tool: string;
11
+ sessionId: string;
12
+ label: string;
13
+ maxBytesPerTurn: number;
14
+ retentionDays: number;
15
+ }
16
+
17
+ export interface RawStreamLogHandle {
18
+ filePath: string;
19
+ writeLine(line: string): void;
20
+ close(options: { keep: boolean }): Promise<void>;
21
+ }
22
+
23
+ export function sanitizeLogPathSegment(value: string): string {
24
+ const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^[._]+|_+$/g, "");
25
+ return safe || "unknown";
26
+ }
27
+
28
+ async function cleanupOldRawStreamLogs(rootDir: string, retentionDays: number): Promise<void> {
29
+ if (!Number.isFinite(retentionDays) || retentionDays <= 0) return;
30
+ const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
31
+
32
+ const visit = async (dir: string): Promise<void> => {
33
+ let entries: Dirent[];
34
+ try {
35
+ entries = await readdir(dir, { withFileTypes: true });
36
+ } catch {
37
+ return;
38
+ }
39
+
40
+ await Promise.all(entries.map(async (entry) => {
41
+ const path = join(dir, entry.name);
42
+ if (entry.isDirectory()) {
43
+ await visit(path);
44
+ try {
45
+ await rm(path, { recursive: false });
46
+ } catch {
47
+ // Directory is not empty or already gone.
48
+ }
49
+ return;
50
+ }
51
+
52
+ if (!entry.isFile()) return;
53
+ try {
54
+ const info = await stat(path);
55
+ if (info.mtimeMs < cutoff) await rm(path, { force: true });
56
+ } catch {
57
+ // Best-effort cleanup only.
58
+ }
59
+ }));
60
+ };
61
+
62
+ await visit(rootDir);
63
+ }
64
+
65
+ export async function createRawStreamLog(options: RawStreamLogOptions): Promise<RawStreamLogHandle | null> {
66
+ if (!options.enabled) return null;
67
+
68
+ const maxBytes = Math.max(0, Math.floor(options.maxBytesPerTurn));
69
+ const tool = sanitizeLogPathSegment(options.tool);
70
+ const session = sanitizeLogPathSegment(options.sessionId);
71
+ const label = sanitizeLogPathSegment(options.label);
72
+ const dir = join(options.rootDir, tool, session);
73
+ await mkdir(dir, { recursive: true });
74
+ void cleanupOldRawStreamLogs(join(options.rootDir, tool), options.retentionDays);
75
+
76
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
77
+ const filePath = join(dir, `${timestamp}-${label}.jsonl.gz`);
78
+ const output = createWriteStream(filePath);
79
+ const gzip = createGzip();
80
+ gzip.pipe(output);
81
+
82
+ let bytes = 0;
83
+ let truncated = false;
84
+ let ended = false;
85
+
86
+ const writeLine = (line: string): void => {
87
+ if (ended || truncated) return;
88
+ const payload = `${line}\n`;
89
+ const payloadBytes = Buffer.byteLength(payload, "utf-8");
90
+ if (maxBytes > 0 && bytes + payloadBytes > maxBytes) {
91
+ const marker = JSON.stringify({
92
+ type: "chatccc_raw_stream_log_truncated",
93
+ reason: "max_bytes_per_turn_exceeded",
94
+ maxBytesPerTurn: maxBytes,
95
+ writtenBytes: bytes,
96
+ });
97
+ gzip.write(`${marker}\n`);
98
+ truncated = true;
99
+ return;
100
+ }
101
+ bytes += payloadBytes;
102
+ gzip.write(payload);
103
+ };
104
+
105
+ const close = async ({ keep }: { keep: boolean }): Promise<void> => {
106
+ if (ended) return;
107
+ ended = true;
108
+ gzip.end();
109
+ try {
110
+ await once(output, "finish");
111
+ } catch {
112
+ // Ignore close errors; caller cannot recover from debug log failures.
113
+ }
114
+ if (!keep) {
115
+ try {
116
+ await unlink(filePath);
117
+ } catch {
118
+ // Best-effort cleanup only.
119
+ }
120
+ }
121
+ };
122
+
123
+ return { filePath, writeLine, close };
124
+ }
@@ -1,141 +1,141 @@
1
- // =============================================================================
2
- // resource-monitor.ts — CLI 进程资源监控(CPU + 内存)
3
- // =============================================================================
4
- // 对所有 chatccc 启动的 CLI 进程持续监控 CPU 和内存占用。
5
- // 若连续 3 分钟两项指标均无变化,判定为僵死,发出 "stuck" 事件。
6
- // =============================================================================
7
-
8
- import { exec } from "node:child_process";
9
- import { EventEmitter } from "node:events";
10
-
11
- const CHECK_INTERVAL_MS = 30_000; // 30 秒检查一次
12
- const STUCK_THRESHOLD = 6; // 连续 6 次无变化 = 3 分钟
13
-
14
- interface ProcessSnapshot {
15
- cpu: number;
16
- memory: number;
17
- unchangedCount: number;
18
- }
19
-
20
- interface TrackedProcess {
21
- pid: number;
22
- sessionId: string;
23
- snapshot: ProcessSnapshot;
24
- }
25
-
26
- export const resourceMonitor = new EventEmitter();
27
-
28
- const tracked = new Map<number, TrackedProcess>();
29
-
30
- let timer: ReturnType<typeof setInterval> | null = null;
31
-
32
- function startIfNeeded(): void {
33
- if (timer) return;
34
- timer = setInterval(checkAll, CHECK_INTERVAL_MS);
35
- timer.unref?.();
36
- }
37
-
38
- function stopIfIdle(): void {
39
- if (tracked.size > 0) return;
40
- if (timer) { clearInterval(timer); timer = null; }
41
- }
42
-
43
- export function registerProcess(pid: number, sessionId: string): void {
44
- tracked.set(pid, { pid, sessionId, snapshot: { cpu: -1, memory: -1, unchangedCount: 0 } });
45
- startIfNeeded();
46
- }
47
-
48
- export function unregisterProcess(pid: number): void {
49
- tracked.delete(pid);
50
- stopIfIdle();
51
- }
52
-
53
- // ---------------------------------------------------------------------------
54
- // 批量查询进程指标(Windows PowerShell)
55
- // ---------------------------------------------------------------------------
56
-
57
- function execPowerShell(script: string, timeoutMs: number): Promise<string> {
58
- return new Promise((resolve, reject) => {
59
- exec(
60
- `powershell -NoProfile -Command "${script}"`,
61
- { timeout: timeoutMs, windowsHide: true },
62
- (err, stdout) => {
63
- if (err) reject(err);
64
- else resolve(stdout);
65
- },
66
- );
67
- });
68
- }
69
-
70
- async function getProcessMetrics(pids: number[]): Promise<Map<number, { cpu: number; memory: number }>> {
71
- const result = new Map<number, { cpu: number; memory: number }>();
72
- if (pids.length === 0) return result;
73
-
74
- const psScript = `Get-Process -Id ${pids.join(",")} -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Id)|$($_.CPU)|$($_.WorkingSet64)" }`;
75
-
76
- try {
77
- const stdout = await execPowerShell(psScript, 10_000);
78
- for (const line of stdout.trim().split(/\r?\n/)) {
79
- const trimmed = line.trim();
80
- if (!trimmed) continue;
81
- const [idStr, cpuStr, memStr] = trimmed.split("|");
82
- const id = parseInt(idStr, 10);
83
- const cpu = parseFloat(cpuStr);
84
- const memory = parseInt(memStr, 10);
85
- if (!isNaN(id) && !isNaN(cpu) && !isNaN(memory)) {
86
- result.set(id, { cpu, memory });
87
- }
88
- }
89
- } catch {
90
- // PowerShell 查询失败时跳过本轮,下次重试
91
- }
92
-
93
- return result;
94
- }
95
-
96
- // ---------------------------------------------------------------------------
97
- // 定时检查
98
- // ---------------------------------------------------------------------------
99
-
100
- async function checkAll(): Promise<void> {
101
- if (tracked.size === 0) return;
102
-
103
- const pids = [...tracked.keys()];
104
- const metrics = await getProcessMetrics(pids);
105
-
106
- for (const [pid, tp] of tracked) {
107
- const m = metrics.get(pid);
108
- if (!m) {
109
- // 进程已不存在,停止追踪
110
- tracked.delete(pid);
111
- continue;
112
- }
113
-
114
- const prev = tp.snapshot;
115
- const cpuChanged = m.cpu !== prev.cpu;
116
- // 内存允许 ±1% 波动,避免正常抖动触发误判
117
- const memTolerance = prev.memory > 0 ? Math.max(prev.memory * 0.01, 1024 * 1024) : 1024 * 1024;
118
- const memChanged = Math.abs(m.memory - prev.memory) > memTolerance;
119
-
120
- if (!cpuChanged && !memChanged) {
121
- tp.snapshot.unchangedCount++;
122
- if (tp.snapshot.unchangedCount >= STUCK_THRESHOLD) {
123
- const idleMinutes = Math.round(
124
- (tp.snapshot.unchangedCount * CHECK_INTERVAL_MS) / 60_000,
125
- );
126
- resourceMonitor.emit("stuck", {
127
- pid: tp.pid,
128
- sessionId: tp.sessionId,
129
- idleMinutes,
130
- });
131
- tracked.delete(pid);
132
- }
133
- } else {
134
- tp.snapshot.unchangedCount = 0;
135
- }
136
- tp.snapshot.cpu = m.cpu;
137
- tp.snapshot.memory = m.memory;
138
- }
139
-
140
- stopIfIdle();
1
+ // =============================================================================
2
+ // resource-monitor.ts — CLI 进程资源监控(CPU + 内存)
3
+ // =============================================================================
4
+ // 对所有 chatccc 启动的 CLI 进程持续监控 CPU 和内存占用。
5
+ // 若连续 3 分钟两项指标均无变化,判定为僵死,发出 "stuck" 事件。
6
+ // =============================================================================
7
+
8
+ import { exec } from "node:child_process";
9
+ import { EventEmitter } from "node:events";
10
+
11
+ const CHECK_INTERVAL_MS = 30_000; // 30 秒检查一次
12
+ const STUCK_THRESHOLD = 6; // 连续 6 次无变化 = 3 分钟
13
+
14
+ interface ProcessSnapshot {
15
+ cpu: number;
16
+ memory: number;
17
+ unchangedCount: number;
18
+ }
19
+
20
+ interface TrackedProcess {
21
+ pid: number;
22
+ sessionId: string;
23
+ snapshot: ProcessSnapshot;
24
+ }
25
+
26
+ export const resourceMonitor = new EventEmitter();
27
+
28
+ const tracked = new Map<number, TrackedProcess>();
29
+
30
+ let timer: ReturnType<typeof setInterval> | null = null;
31
+
32
+ function startIfNeeded(): void {
33
+ if (timer) return;
34
+ timer = setInterval(checkAll, CHECK_INTERVAL_MS);
35
+ timer.unref?.();
36
+ }
37
+
38
+ function stopIfIdle(): void {
39
+ if (tracked.size > 0) return;
40
+ if (timer) { clearInterval(timer); timer = null; }
41
+ }
42
+
43
+ export function registerProcess(pid: number, sessionId: string): void {
44
+ tracked.set(pid, { pid, sessionId, snapshot: { cpu: -1, memory: -1, unchangedCount: 0 } });
45
+ startIfNeeded();
46
+ }
47
+
48
+ export function unregisterProcess(pid: number): void {
49
+ tracked.delete(pid);
50
+ stopIfIdle();
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // 批量查询进程指标(Windows PowerShell)
55
+ // ---------------------------------------------------------------------------
56
+
57
+ function execPowerShell(script: string, timeoutMs: number): Promise<string> {
58
+ return new Promise((resolve, reject) => {
59
+ exec(
60
+ `powershell -NoProfile -Command "${script}"`,
61
+ { timeout: timeoutMs, windowsHide: true },
62
+ (err, stdout) => {
63
+ if (err) reject(err);
64
+ else resolve(stdout);
65
+ },
66
+ );
67
+ });
68
+ }
69
+
70
+ async function getProcessMetrics(pids: number[]): Promise<Map<number, { cpu: number; memory: number }>> {
71
+ const result = new Map<number, { cpu: number; memory: number }>();
72
+ if (pids.length === 0) return result;
73
+
74
+ const psScript = `Get-Process -Id ${pids.join(",")} -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Id)|$($_.CPU)|$($_.WorkingSet64)" }`;
75
+
76
+ try {
77
+ const stdout = await execPowerShell(psScript, 10_000);
78
+ for (const line of stdout.trim().split(/\r?\n/)) {
79
+ const trimmed = line.trim();
80
+ if (!trimmed) continue;
81
+ const [idStr, cpuStr, memStr] = trimmed.split("|");
82
+ const id = parseInt(idStr, 10);
83
+ const cpu = parseFloat(cpuStr);
84
+ const memory = parseInt(memStr, 10);
85
+ if (!isNaN(id) && !isNaN(cpu) && !isNaN(memory)) {
86
+ result.set(id, { cpu, memory });
87
+ }
88
+ }
89
+ } catch {
90
+ // PowerShell 查询失败时跳过本轮,下次重试
91
+ }
92
+
93
+ return result;
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // 定时检查
98
+ // ---------------------------------------------------------------------------
99
+
100
+ async function checkAll(): Promise<void> {
101
+ if (tracked.size === 0) return;
102
+
103
+ const pids = [...tracked.keys()];
104
+ const metrics = await getProcessMetrics(pids);
105
+
106
+ for (const [pid, tp] of tracked) {
107
+ const m = metrics.get(pid);
108
+ if (!m) {
109
+ // 进程已不存在,停止追踪
110
+ tracked.delete(pid);
111
+ continue;
112
+ }
113
+
114
+ const prev = tp.snapshot;
115
+ const cpuChanged = m.cpu !== prev.cpu;
116
+ // 内存允许 ±1% 波动,避免正常抖动触发误判
117
+ const memTolerance = prev.memory > 0 ? Math.max(prev.memory * 0.01, 1024 * 1024) : 1024 * 1024;
118
+ const memChanged = Math.abs(m.memory - prev.memory) > memTolerance;
119
+
120
+ if (!cpuChanged && !memChanged) {
121
+ tp.snapshot.unchangedCount++;
122
+ if (tp.snapshot.unchangedCount >= STUCK_THRESHOLD) {
123
+ const idleMinutes = Math.round(
124
+ (tp.snapshot.unchangedCount * CHECK_INTERVAL_MS) / 60_000,
125
+ );
126
+ resourceMonitor.emit("stuck", {
127
+ pid: tp.pid,
128
+ sessionId: tp.sessionId,
129
+ idleMinutes,
130
+ });
131
+ tracked.delete(pid);
132
+ }
133
+ } else {
134
+ tp.snapshot.unchangedCount = 0;
135
+ }
136
+ tp.snapshot.cpu = m.cpu;
137
+ tp.snapshot.memory = m.memory;
138
+ }
139
+
140
+ stopIfIdle();
141
141
  }