@chatcode/chatcode-cli-test 1.0.41 → 1.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { spawnSync } from 'node:child_process'
3
2
  import { existsSync, readFileSync } from 'node:fs'
4
3
  import { createRequire } from 'node:module'
5
4
  import { dirname, join } from 'node:path'
6
5
  import { fileURLToPath } from 'node:url'
6
+ import { runSupervised } from './generation-supervisor.js'
7
7
 
8
8
  const require = createRequire(import.meta.url)
9
9
  const here = dirname(fileURLToPath(import.meta.url))
@@ -16,8 +16,17 @@ const mainPackage = JSON.parse(
16
16
  )
17
17
  const mainPackageName = mainPackage.name
18
18
 
19
+ function shouldUseNativeBinary() {
20
+ if (process.env.CHATCODE_CLI_FORCE_NODE === '1') return false
21
+ return (
22
+ process.env.CHATCODE_CLI_USE_NATIVE === '1' ||
23
+ process.env.CHATCODE_CLI_FORCE_NATIVE === '1'
24
+ )
25
+ }
26
+
19
27
  function resolveOptionalBinary() {
20
28
  if (process.env.CHATCODE_CLI_FORCE_NODE === '1') return null
29
+ if (!shouldUseNativeBinary()) return null
21
30
 
22
31
  try {
23
32
  const packageName = `${mainPackageName}-${platformKey}`
@@ -43,16 +52,14 @@ function resolveOptionalBinary() {
43
52
 
44
53
  function resolveLocalBinary() {
45
54
  if (process.env.CHATCODE_CLI_FORCE_NODE === '1') return null
55
+ if (!shouldUseNativeBinary()) return null
46
56
 
47
57
  const localBinary = join(root, 'dist', 'native', platformKey, executableName)
48
58
  return existsSync(localBinary) ? localBinary : null
49
59
  }
50
60
 
51
61
  function run(command, commandArgs) {
52
- const result = spawnSync(command, commandArgs, {
53
- stdio: 'inherit',
54
- windowsHide: false,
55
- })
62
+ const result = runSupervised(command, commandArgs)
56
63
 
57
64
  if (result.error) return result.error
58
65
  if (result.signal) {
@@ -82,6 +89,10 @@ if (!existsSync(fallbackCli)) {
82
89
  const error = run(process.execPath, [
83
90
  '--max-old-space-size=4096',
84
91
  '--stack_size=4096',
92
+ // The REPL only calls global.gc() after an idle rebuildable-cache release. This
93
+ // lets V8 reclaim the now-unreachable graph promptly instead of waiting for
94
+ // a future allocation to discover it.
95
+ '--expose-gc',
85
96
  fallbackCli,
86
97
  ...args,
87
98
  ])
@@ -0,0 +1,131 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { randomUUID } from 'node:crypto'
3
+ import {
4
+ existsSync,
5
+ readFileSync,
6
+ statSync,
7
+ unlinkSync,
8
+ } from 'node:fs'
9
+ import { tmpdir } from 'node:os'
10
+ import { isAbsolute, join, resolve } from 'node:path'
11
+
12
+ export const PROCESS_GENERATION_ROTATE_EXIT_CODE = 75
13
+ export const GENERATION_CONTROL_FILE_ENV =
14
+ 'CHATCODE_CLI_GENERATION_CONTROL_FILE'
15
+ export const GENERATION_CONTROL_TOKEN_ENV =
16
+ 'CHATCODE_CLI_GENERATION_CONTROL_TOKEN'
17
+ export const INTERNAL_RESUME_SESSION_ENV =
18
+ 'CHATCODE_CLI_INTERNAL_RESUME_SESSION_ID'
19
+ export const PROCESS_GENERATION_INDEX_ENV =
20
+ 'CHATCODE_CLI_PROCESS_GENERATION_INDEX'
21
+ export const GENERATION_SUPERVISOR_PID_ENV =
22
+ 'CHATCODE_CLI_GENERATION_SUPERVISOR_PID'
23
+
24
+ const UUID_PATTERN =
25
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
26
+ const MAX_CONTROL_FILE_BYTES = 8 * 1024
27
+
28
+ function removeControlFile(controlFile) {
29
+ try {
30
+ if (existsSync(controlFile)) unlinkSync(controlFile)
31
+ } catch {
32
+ // The file is private, contains no conversation content, and lives only
33
+ // for this supervisor lifetime. Failure to remove it must not hide the
34
+ // child process's real exit status.
35
+ }
36
+ }
37
+
38
+ function readRotationRequest(controlFile, token) {
39
+ try {
40
+ const stat = statSync(controlFile)
41
+ if (!stat.isFile() || stat.size <= 0 || stat.size > MAX_CONTROL_FILE_BYTES) {
42
+ return null
43
+ }
44
+
45
+ const request = JSON.parse(readFileSync(controlFile, 'utf8'))
46
+ if (
47
+ request?.version !== 1 ||
48
+ request.token !== token ||
49
+ typeof request.sessionId !== 'string' ||
50
+ !UUID_PATTERN.test(request.sessionId) ||
51
+ typeof request.cwd !== 'string' ||
52
+ request.cwd.length === 0
53
+ ) {
54
+ return null
55
+ }
56
+
57
+ const cwd = isAbsolute(request.cwd)
58
+ ? resolve(request.cwd)
59
+ : resolve(process.cwd(), request.cwd)
60
+ if (!statSync(cwd).isDirectory()) return null
61
+
62
+ return { sessionId: request.sessionId, cwd }
63
+ } catch {
64
+ return null
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Run a ChatCode worker and replace it only after an authenticated, explicit
70
+ * generation-rotation request. The supervisor itself holds no transcript or
71
+ * UI graph, so replacing the worker returns JSC/V8, Yoga/WASM and native
72
+ * allocator high-water pages to the operating system.
73
+ */
74
+ export function runSupervised(command, commandArgs) {
75
+ const token = randomUUID()
76
+ const controlFile = join(
77
+ tmpdir(),
78
+ `chatcode-cli-generation-${process.pid}-${randomUUID()}.json`,
79
+ )
80
+ let cwd = process.cwd()
81
+ let resumeSessionId
82
+ let generation = 0
83
+
84
+ try {
85
+ while (true) {
86
+ removeControlFile(controlFile)
87
+ const env = {
88
+ ...process.env,
89
+ [GENERATION_CONTROL_FILE_ENV]: controlFile,
90
+ [GENERATION_CONTROL_TOKEN_ENV]: token,
91
+ [PROCESS_GENERATION_INDEX_ENV]: String(generation),
92
+ [GENERATION_SUPERVISOR_PID_ENV]: String(process.pid),
93
+ }
94
+ // Never trust an inherited internal-resume value. Only a request whose
95
+ // one-time control token was validated above can populate this channel.
96
+ delete env[INTERNAL_RESUME_SESSION_ENV]
97
+ if (resumeSessionId) {
98
+ env[INTERNAL_RESUME_SESSION_ENV] = resumeSessionId
99
+ }
100
+
101
+ const result = spawnSync(command, commandArgs, {
102
+ cwd,
103
+ env,
104
+ stdio: 'inherit',
105
+ windowsHide: false,
106
+ })
107
+
108
+ if (
109
+ result.error ||
110
+ result.signal ||
111
+ result.status !== PROCESS_GENERATION_ROTATE_EXIT_CODE
112
+ ) {
113
+ return result
114
+ }
115
+
116
+ const request = readRotationRequest(controlFile, token)
117
+ if (!request) {
118
+ console.error(
119
+ 'ChatCode CLI rejected an invalid process-generation rotation request.',
120
+ )
121
+ return result
122
+ }
123
+
124
+ resumeSessionId = request.sessionId
125
+ cwd = request.cwd
126
+ generation += 1
127
+ }
128
+ } finally {
129
+ removeControlFile(controlFile)
130
+ }
131
+ }