@muyichengshayu/promptx 0.2.13 → 0.2.15

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 (52) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/apps/server/src/agentSessionDiscovery.js +180 -7
  3. package/apps/web/dist/assets/{CodexSessionManagerDialog-Dic9kMHK.js → CodexSessionManagerDialog-y7O-JTxP.js} +1 -1
  4. package/apps/web/dist/assets/{TaskDiffReviewDialog-CKiZdXqi.js → TaskDiffReviewDialog-CTr_zoAn.js} +1 -1
  5. package/apps/web/dist/assets/{WorkbenchSettingsDialog-CP0z90bm.js → WorkbenchSettingsDialog-Bf2DCuN_.js} +1 -1
  6. package/apps/web/dist/assets/{WorkbenchView-D1oxqNr4.css → WorkbenchView-CK1snPBz.css} +1 -1
  7. package/apps/web/dist/assets/WorkbenchView-Gq3mmtsK.js +60 -0
  8. package/apps/web/dist/assets/index-Co1Ssha9.js +2 -0
  9. package/apps/web/dist/index.html +1 -1
  10. package/package.json +21 -14
  11. package/apps/runner/src/engines/claudeCodeRunner.test.js +0 -467
  12. package/apps/runner/src/engines/kimiCodeRunner.test.js +0 -127
  13. package/apps/runner/src/engines/openCodeRunner.test.js +0 -236
  14. package/apps/runner/src/engines/runnerContract.test.js +0 -449
  15. package/apps/runner/src/engines/shellRunner.test.js +0 -46
  16. package/apps/runner/src/runManager.test.js +0 -913
  17. package/apps/runner/src/serverClient.test.js +0 -93
  18. package/apps/server/src/agentSessionDiscovery.test.js +0 -186
  19. package/apps/server/src/appPaths.test.js +0 -52
  20. package/apps/server/src/assetRoutes.test.js +0 -168
  21. package/apps/server/src/codex.test.js +0 -518
  22. package/apps/server/src/codexRoutes.test.js +0 -376
  23. package/apps/server/src/codexRuns.test.js +0 -160
  24. package/apps/server/src/codexSessions.test.js +0 -369
  25. package/apps/server/src/db.test.js +0 -182
  26. package/apps/server/src/gitDiff.test.js +0 -542
  27. package/apps/server/src/gitDiffClient.test.js +0 -140
  28. package/apps/server/src/internalRoutes.test.js +0 -134
  29. package/apps/server/src/maintenance.test.js +0 -154
  30. package/apps/server/src/processControl.test.js +0 -147
  31. package/apps/server/src/relayClient.test.js +0 -478
  32. package/apps/server/src/relayConfig.test.js +0 -73
  33. package/apps/server/src/relayProtocol.test.js +0 -49
  34. package/apps/server/src/relayServer.test.js +0 -798
  35. package/apps/server/src/relayTenants.test.js +0 -137
  36. package/apps/server/src/relayUsageStore.test.js +0 -65
  37. package/apps/server/src/repository.test.js +0 -150
  38. package/apps/server/src/runDispatchService.test.js +0 -563
  39. package/apps/server/src/runEventIngest.test.js +0 -225
  40. package/apps/server/src/runRecovery.test.js +0 -73
  41. package/apps/server/src/runnerClient.test.js +0 -80
  42. package/apps/server/src/runnerDispatch.test.js +0 -136
  43. package/apps/server/src/systemConfig.test.js +0 -112
  44. package/apps/server/src/systemRoutes.test.js +0 -319
  45. package/apps/server/src/taskRoutes.test.js +0 -775
  46. package/apps/server/src/upload.test.js +0 -30
  47. package/apps/server/src/webAppRoutes.test.js +0 -67
  48. package/apps/server/src/workspaceFiles.test.js +0 -279
  49. package/apps/web/dist/assets/WorkbenchView-noayQwj4.js +0 -60
  50. package/apps/web/dist/assets/index-HLkdzIYF.js +0 -2
  51. package/packages/shared/src/dailyLogStream.test.js +0 -29
  52. package/packages/shared/src/shellCommands.test.js +0 -45
@@ -1,225 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import fs from 'node:fs'
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import test from 'node:test'
6
-
7
- test('runEventIngest 会写入事件、同步 session 更新并推进 run 状态', async () => {
8
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-run-ingest-'))
9
- const originalCwd = process.cwd()
10
- const originalDataDir = process.env.PROMPTX_DATA_DIR
11
- const dataDir = path.join(tempDir, 'data')
12
- fs.mkdirSync(dataDir, { recursive: true })
13
- process.chdir(tempDir)
14
- process.env.PROMPTX_DATA_DIR = dataDir
15
-
16
- try {
17
- const suffix = `test=${Date.now()}`
18
- const { run } = await import(`./db.js?${suffix}`)
19
- const { createRunEventIngestService } = await import(`./runEventIngest.js?${suffix}`)
20
- const { getCodexRunById, listCodexRunEvents } = await import(`./codexRuns.js?${suffix}`)
21
- const { getPromptxCodexSessionById } = await import(`./codexSessions.js?${suffix}`)
22
-
23
- const now = new Date().toISOString()
24
- run(
25
- `INSERT INTO tasks (slug, edit_token, title, auto_title, last_prompt_preview, codex_session_id, visibility, expires_at, created_at, updated_at)
26
- VALUES (?, ?, '', '', '', ?, 'private', NULL, ?, ?)`,
27
- ['task-1', 'token-1', 'session-1', now, now]
28
- )
29
- run(
30
- `INSERT INTO codex_sessions (id, title, engine, cwd, codex_thread_id, engine_session_id, engine_thread_id, engine_meta_json, created_at, updated_at)
31
- VALUES (?, ?, 'codex', ?, '', '', '', '{}', ?, ?)`,
32
- ['session-1', 'Session 1', tempDir, now, now]
33
- )
34
- run(
35
- `INSERT INTO codex_sessions (id, title, engine, cwd, codex_thread_id, engine_session_id, engine_thread_id, engine_meta_json, created_at, updated_at)
36
- VALUES (?, ?, 'claude-code', ?, '', 'claude-1', 'claude-1', ?, ?, ?)`,
37
- ['session-2', 'Session 1', tempDir, JSON.stringify({ hidden: true, projectRootId: 'session-1' }), now, now]
38
- )
39
- run(
40
- `INSERT INTO codex_sessions (id, title, engine, cwd, codex_thread_id, engine_session_id, engine_thread_id, engine_meta_json, created_at, updated_at)
41
- VALUES (?, ?, 'opencode', ?, '', 'opencode-1', 'opencode-1', ?, ?, ?)`,
42
- ['session-3', 'Session 1', tempDir, JSON.stringify({ hidden: true, projectRootId: 'session-1' }), now, now]
43
- )
44
- run(
45
- `INSERT INTO codex_runs (id, task_slug, session_id, engine, prompt, prompt_blocks_json, status, response_message, error_message, created_at, updated_at, started_at, finished_at)
46
- VALUES (?, ?, ?, 'codex', ?, '[]', 'queued', '', '', ?, ?, NULL, NULL)`,
47
- ['run-1', 'task-1', 'session-1', 'hello', now, now]
48
- )
49
-
50
- const broadcasts = []
51
- const ingest = createRunEventIngestService({
52
- broadcastServerEvent(type, payload = {}) {
53
- broadcasts.push({ type, ...payload })
54
- },
55
- })
56
-
57
- const eventsResult = ingest.ingestEvents([
58
- {
59
- runId: 'run-1',
60
- seq: 1,
61
- type: 'session.updated',
62
- ts: now,
63
- payload: {
64
- type: 'session.updated',
65
- session: {
66
- id: 'session-1',
67
- codexThreadId: 'thread-1',
68
- engineThreadId: 'thread-1',
69
- updatedAt: now,
70
- },
71
- },
72
- },
73
- {
74
- runId: 'run-1',
75
- seq: 2,
76
- type: 'stdout',
77
- ts: now,
78
- payload: {
79
- type: 'stdout',
80
- text: 'hello world',
81
- },
82
- },
83
- ])
84
-
85
- assert.equal(eventsResult.ok, true)
86
- assert.equal(listCodexRunEvents('run-1')?.length, 2)
87
- assert.equal(getPromptxCodexSessionById('session-1')?.engineThreadId, 'thread-1')
88
- assert.deepEqual(
89
- listCodexRunEvents('run-1')?.[0]?.payload?.session?.agentBindings?.map((item) => item.engine),
90
- ['codex', 'claude-code', 'opencode']
91
- )
92
- assert.deepEqual(
93
- broadcasts
94
- .filter((item) => item.type === 'run.event' && item.runId === 'run-1')
95
- .map((item) => item.event?.payload?.session?.agentBindings?.map((binding) => binding.engine))
96
- .find(Boolean),
97
- ['codex', 'claude-code', 'opencode']
98
- )
99
-
100
- const runningRun = ingest.ingestStatus({
101
- runId: 'run-1',
102
- status: 'running',
103
- startedAt: now,
104
- heartbeatAt: now,
105
- })
106
- assert.equal(runningRun?.status, 'running')
107
-
108
- const completedRun = ingest.ingestStatus({
109
- runId: 'run-1',
110
- status: 'completed',
111
- responseMessage: 'done',
112
- finishedAt: now,
113
- heartbeatAt: now,
114
- session: {
115
- id: 'session-1',
116
- codexThreadId: 'thread-1',
117
- engineThreadId: 'thread-1',
118
- updatedAt: now,
119
- },
120
- })
121
-
122
- const storedRun = getCodexRunById('run-1')
123
- assert.equal(completedRun?.status, 'completed')
124
- assert.equal(storedRun?.status, 'completed')
125
- assert.equal(storedRun?.responseMessage, 'done')
126
- assert.equal(getPromptxCodexSessionById('session-1')?.engineThreadId, 'thread-1')
127
- assert.ok(broadcasts.some((item) => item.type === 'run.event' && item.runId === 'run-1'))
128
- assert.ok(broadcasts.some((item) => item.type === 'runs.changed' && item.runId === 'run-1'))
129
- assert.ok(broadcasts.some((item) => item.type === 'sessions.changed' && item.sessionId === 'session-1'))
130
-
131
- const lateHeartbeatAt = new Date(Date.now() + 1000).toISOString()
132
- const staleRun = ingest.ingestStatus({
133
- runId: 'run-1',
134
- status: 'running',
135
- responseMessage: 'should-be-ignored',
136
- heartbeatAt: lateHeartbeatAt,
137
- session: {
138
- id: 'session-1',
139
- codexThreadId: 'thread-1',
140
- engineThreadId: 'thread-1',
141
- updatedAt: lateHeartbeatAt,
142
- },
143
- })
144
-
145
- const storedRunAfterLateHeartbeat = getCodexRunById('run-1')
146
- assert.equal(staleRun?.status, 'completed')
147
- assert.equal(storedRunAfterLateHeartbeat?.status, 'completed')
148
- assert.equal(storedRunAfterLateHeartbeat?.responseMessage, 'done')
149
- } finally {
150
- process.chdir(originalCwd)
151
- if (typeof originalDataDir === 'string') {
152
- process.env.PROMPTX_DATA_DIR = originalDataDir
153
- } else {
154
- delete process.env.PROMPTX_DATA_DIR
155
- }
156
- }
157
- })
158
-
159
- test('runEventIngest 忽略 shell session 的身份字段补丁', async () => {
160
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-run-ingest-shell-'))
161
- const originalCwd = process.cwd()
162
- const originalDataDir = process.env.PROMPTX_DATA_DIR
163
- const dataDir = path.join(tempDir, 'data')
164
- fs.mkdirSync(dataDir, { recursive: true })
165
- process.chdir(tempDir)
166
- process.env.PROMPTX_DATA_DIR = dataDir
167
-
168
- try {
169
- const suffix = `test=${Date.now()}-${Math.random().toString(36).slice(2)}`
170
- const { get, run } = await import(`./db.js?${suffix}`)
171
- const { createRunEventIngestService } = await import(`./runEventIngest.js?${suffix}`)
172
-
173
- const now = new Date().toISOString()
174
- const sessionId = 'session-shell-only'
175
- const taskSlug = 'task-shell-only'
176
- const runId = 'run-shell-only'
177
- run(
178
- `INSERT INTO tasks (slug, edit_token, title, auto_title, last_prompt_preview, codex_session_id, visibility, expires_at, created_at, updated_at)
179
- VALUES (?, ?, '', '', '', ?, 'private', NULL, ?, ?)`,
180
- [taskSlug, 'token-shell', sessionId, now, now]
181
- )
182
- run(
183
- `INSERT INTO codex_sessions (id, title, engine, cwd, codex_thread_id, engine_session_id, engine_thread_id, engine_meta_json, created_at, updated_at)
184
- VALUES (?, ?, 'codex', ?, ?, '', ?, '{}', ?, ?)`,
185
- [sessionId, 'Session 1', tempDir, 'thread-original', 'thread-original', now, now]
186
- )
187
- run(
188
- `INSERT INTO codex_runs (id, task_slug, session_id, engine, prompt, prompt_blocks_json, status, response_message, error_message, created_at, updated_at, started_at, finished_at)
189
- VALUES (?, ?, ?, 'shell', ?, '[]', 'queued', '', '', ?, ?, NULL, NULL)`,
190
- [runId, taskSlug, sessionId, '!pwd', now, now]
191
- )
192
-
193
- const ingest = createRunEventIngestService()
194
- ingest.ingestStatus({
195
- runId,
196
- status: 'completed',
197
- responseMessage: '/tmp/demo',
198
- finishedAt: now,
199
- heartbeatAt: now,
200
- session: {
201
- id: sessionId,
202
- engine: 'shell',
203
- codexThreadId: 'thread-shell-overwrite',
204
- engineThreadId: 'thread-shell-overwrite',
205
- updatedAt: now,
206
- },
207
- })
208
-
209
- const row = get(
210
- `SELECT codex_thread_id, engine_thread_id
211
- FROM codex_sessions
212
- WHERE id = ?`,
213
- [sessionId]
214
- )
215
- assert.equal(row?.codex_thread_id, 'thread-original')
216
- assert.equal(row?.engine_thread_id, 'thread-original')
217
- } finally {
218
- process.chdir(originalCwd)
219
- if (typeof originalDataDir === 'string') {
220
- process.env.PROMPTX_DATA_DIR = originalDataDir
221
- } else {
222
- delete process.env.PROMPTX_DATA_DIR
223
- }
224
- }
225
- })
@@ -1,73 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import fs from 'node:fs'
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import test from 'node:test'
6
-
7
- test('runRecovery 会回收失联的 active run,并按状态落到 error / stop_timeout', async () => {
8
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-run-recovery-'))
9
- const originalCwd = process.cwd()
10
- const originalDataDir = process.env.PROMPTX_DATA_DIR
11
- const dataDir = path.join(tempDir, 'data')
12
- fs.mkdirSync(dataDir, { recursive: true })
13
- process.chdir(tempDir)
14
- process.env.PROMPTX_DATA_DIR = dataDir
15
-
16
- try {
17
- const suffix = `test=${Date.now()}`
18
- const { run } = await import(`./db.js?${suffix}`)
19
- const { getCodexRunById } = await import(`./codexRuns.js?${suffix}`)
20
- const { createRunRecoveryService } = await import(`./runRecovery.js?${suffix}`)
21
-
22
- const staleAt = new Date(Date.now() - 60_000).toISOString()
23
- run(
24
- `INSERT INTO tasks (slug, edit_token, title, auto_title, last_prompt_preview, codex_session_id, visibility, expires_at, created_at, updated_at)
25
- VALUES (?, ?, '', '', '', ?, 'private', NULL, ?, ?)`,
26
- ['task-1', 'token-1', 'session-1', staleAt, staleAt]
27
- )
28
- run(
29
- `INSERT INTO codex_sessions (id, title, engine, cwd, codex_thread_id, engine_session_id, engine_thread_id, engine_meta_json, created_at, updated_at)
30
- VALUES (?, ?, 'codex', ?, '', '', '', '{}', ?, ?)`,
31
- ['session-1', 'Session 1', tempDir, staleAt, staleAt]
32
- )
33
- run(
34
- `INSERT INTO codex_runs (id, task_slug, session_id, engine, prompt, prompt_blocks_json, status, response_message, error_message, created_at, updated_at, started_at, finished_at)
35
- VALUES (?, ?, ?, 'codex', ?, '[]', 'running', '', '', ?, ?, ?, NULL)`,
36
- ['run-1', 'task-1', 'session-1', 'hello', staleAt, staleAt, staleAt]
37
- )
38
- run(
39
- `INSERT INTO codex_runs (id, task_slug, session_id, engine, prompt, prompt_blocks_json, status, response_message, error_message, created_at, updated_at, started_at, finished_at)
40
- VALUES (?, ?, ?, 'codex', ?, '[]', 'stopping', '', '', ?, ?, ?, NULL)`,
41
- ['run-2', 'task-1', 'session-1', 'hello2', staleAt, staleAt, staleAt]
42
- )
43
-
44
- const recovered = []
45
- const recovery = createRunRecoveryService({
46
- staleThresholdMs: 5000,
47
- onRecoveredRun(runRecord) {
48
- recovered.push(runRecord.id)
49
- },
50
- })
51
-
52
- const sweptRuns = recovery.sweep()
53
- assert.equal(sweptRuns.length, 2)
54
- assert.deepEqual(new Set(recovered), new Set(['run-1', 'run-2']))
55
- assert.equal(getCodexRunById('run-1')?.status, 'error')
56
- assert.equal(getCodexRunById('run-2')?.status, 'stop_timeout')
57
-
58
- const diagnostics = recovery.getDiagnostics()
59
- assert.equal(diagnostics.metrics.totalSweeps, 1)
60
- assert.equal(diagnostics.metrics.totalRecovered, 2)
61
- assert.equal(diagnostics.metrics.totalRecoveredToError, 1)
62
- assert.equal(diagnostics.metrics.totalRecoveredToStopTimeout, 1)
63
- assert.deepEqual(new Set(diagnostics.metrics.lastRecoveredRunIds), new Set(['run-1', 'run-2']))
64
- assert.equal(diagnostics.config.staleThresholdMs, 5000)
65
- } finally {
66
- process.chdir(originalCwd)
67
- if (typeof originalDataDir === 'string') {
68
- process.env.PROMPTX_DATA_DIR = originalDataDir
69
- } else {
70
- delete process.env.PROMPTX_DATA_DIR
71
- }
72
- }
73
- })
@@ -1,80 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import http from 'node:http'
3
- import test from 'node:test'
4
-
5
- import { getInternalAuthHeaderName, getInternalAuthToken } from './internalAuth.js'
6
- import { createRunnerClient } from './runnerClient.js'
7
-
8
- function startJsonServer(handler) {
9
- return new Promise((resolve) => {
10
- const server = http.createServer(handler)
11
- server.listen(0, '127.0.0.1', () => {
12
- const address = server.address()
13
- resolve({
14
- server,
15
- baseUrl: `http://127.0.0.1:${address.port}`,
16
- })
17
- })
18
- })
19
- }
20
-
21
- test('runnerClient attaches internal auth header and parses JSON response', async () => {
22
- const { server, baseUrl } = await startJsonServer((request, response) => {
23
- assert.equal(request.headers[getInternalAuthHeaderName()], getInternalAuthToken())
24
- response.writeHead(200, { 'Content-Type': 'application/json' })
25
- response.end(JSON.stringify({ ok: true }))
26
- })
27
-
28
- try {
29
- const client = createRunnerClient({ baseUrl, timeoutMs: 1000 })
30
- const payload = await client.getDiagnostics()
31
- assert.deepEqual(payload, { ok: true })
32
- } finally {
33
- await new Promise((resolve) => server.close(resolve))
34
- }
35
- })
36
-
37
- test('runnerClient fails fast on timeout', async () => {
38
- const { server, baseUrl } = await startJsonServer((_request, response) => {
39
- setTimeout(() => {
40
- response.writeHead(200, { 'Content-Type': 'application/json' })
41
- response.end(JSON.stringify({ ok: true }))
42
- }, 700)
43
- })
44
-
45
- try {
46
- const client = createRunnerClient({ baseUrl, timeoutMs: 500 })
47
- await assert.rejects(
48
- () => client.getDiagnostics(),
49
- (error) => error?.statusCode === 504 && /超时/.test(String(error?.message || ''))
50
- )
51
- } finally {
52
- await new Promise((resolve) => server.close(resolve))
53
- }
54
- })
55
-
56
- test('runnerClient updates runner config with internal auth header', async () => {
57
- const { server, baseUrl } = await startJsonServer(async (request, response) => {
58
- assert.equal(request.method, 'PUT')
59
- assert.equal(request.url, '/internal/config')
60
- assert.equal(request.headers[getInternalAuthHeaderName()], getInternalAuthToken())
61
-
62
- const chunks = []
63
- for await (const chunk of request) {
64
- chunks.push(chunk)
65
- }
66
- const body = JSON.parse(Buffer.concat(chunks).toString('utf8'))
67
- assert.equal(body.maxConcurrentRuns, 3)
68
-
69
- response.writeHead(200, { 'Content-Type': 'application/json' })
70
- response.end(JSON.stringify({ ok: true, config: { maxConcurrentRuns: 3 } }))
71
- })
72
-
73
- try {
74
- const client = createRunnerClient({ baseUrl, timeoutMs: 1000 })
75
- const payload = await client.updateConfig({ maxConcurrentRuns: 3 })
76
- assert.deepEqual(payload, { ok: true, config: { maxConcurrentRuns: 3 } })
77
- } finally {
78
- await new Promise((resolve) => server.close(resolve))
79
- }
80
- })
@@ -1,136 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import fs from 'node:fs'
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import test from 'node:test'
6
-
7
- const sharedTempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-runner-dispatch-'))
8
- const originalCwd = process.cwd()
9
- const originalDataDir = process.env.PROMPTX_DATA_DIR
10
- const sharedDataDir = path.join(sharedTempDir, 'data')
11
- fs.mkdirSync(sharedDataDir, { recursive: true })
12
- process.chdir(sharedTempDir)
13
- process.env.PROMPTX_DATA_DIR = sharedDataDir
14
-
15
- test.after(() => {
16
- process.chdir(originalCwd)
17
- if (typeof originalDataDir === 'string') {
18
- process.env.PROMPTX_DATA_DIR = originalDataDir
19
- } else {
20
- delete process.env.PROMPTX_DATA_DIR
21
- }
22
- })
23
-
24
- test('extractRunnerDispatchPatch prefers runner payload status and timestamps', async () => {
25
- const { extractRunnerDispatchPatch } = await import(`./runnerDispatch.js?test=${Date.now()}`)
26
-
27
- const patch = extractRunnerDispatchPatch({
28
- status: 'starting',
29
- startedAt: 'outer-start',
30
- run: {
31
- status: 'queued',
32
- startedAt: 'inner-start',
33
- finishedAt: 'inner-finish',
34
- },
35
- }, 'queued')
36
-
37
- assert.deepEqual(patch, {
38
- status: 'queued',
39
- startedAt: 'inner-start',
40
- finishedAt: 'inner-finish',
41
- })
42
- })
43
-
44
- test('reconcileRunAfterRunnerDispatchError syncs run state from runner lookup', async () => {
45
- const suffix = `test=${Date.now()}`
46
- const { run } = await import(`./db.js?${suffix}`)
47
- const { getCodexRunById } = await import(`./codexRuns.js?${suffix}`)
48
- const { reconcileRunAfterRunnerDispatchError } = await import(`./runnerDispatch.js?${suffix}`)
49
-
50
- const now = new Date().toISOString()
51
- run(
52
- `INSERT INTO tasks (slug, edit_token, title, auto_title, last_prompt_preview, codex_session_id, visibility, expires_at, created_at, updated_at)
53
- VALUES (?, ?, '', '', '', ?, 'private', NULL, ?, ?)`,
54
- ['task-1', 'token-1', 'session-1', now, now]
55
- )
56
- run(
57
- `INSERT INTO codex_sessions (id, title, engine, cwd, codex_thread_id, engine_session_id, engine_thread_id, engine_meta_json, created_at, updated_at)
58
- VALUES (?, ?, 'codex', ?, '', '', '', '{}', ?, ?)`,
59
- ['session-1', 'Session 1', sharedTempDir, now, now]
60
- )
61
- run(
62
- `INSERT INTO codex_runs (id, task_slug, session_id, engine, prompt, prompt_blocks_json, status, response_message, error_message, created_at, updated_at, started_at, finished_at)
63
- VALUES (?, ?, ?, 'codex', ?, '[]', 'queued', '', '', ?, ?, NULL, NULL)`,
64
- ['run-1', 'task-1', 'session-1', 'hello', now, now]
65
- )
66
-
67
- const reconciled = await reconcileRunAfterRunnerDispatchError({
68
- runId: 'run-1',
69
- error: Object.assign(new Error('runner request timed out after 5000ms'), { statusCode: 504 }),
70
- runnerClient: {
71
- async getRun() {
72
- return {
73
- run: {
74
- runId: 'run-1',
75
- status: 'running',
76
- startedAt: now,
77
- },
78
- }
79
- },
80
- },
81
- fallbackStatus: 'queued',
82
- logger: {
83
- warn() {},
84
- },
85
- })
86
-
87
- assert.equal(reconciled.pending, false)
88
- assert.equal(reconciled.syncedFromRunner, true)
89
- assert.equal(reconciled.run?.status, 'running')
90
- assert.equal(getCodexRunById('run-1')?.status, 'running')
91
- })
92
-
93
- test('reconcileRunAfterRunnerDispatchError keeps run queued on timeout when runner lookup is unavailable', async () => {
94
- const suffix = `test=${Date.now()}`
95
- const { run } = await import(`./db.js?${suffix}`)
96
- const { getCodexRunById } = await import(`./codexRuns.js?${suffix}`)
97
- const { reconcileRunAfterRunnerDispatchError } = await import(`./runnerDispatch.js?${suffix}`)
98
-
99
- const now = new Date().toISOString()
100
- run(
101
- `INSERT INTO tasks (slug, edit_token, title, auto_title, last_prompt_preview, codex_session_id, visibility, expires_at, created_at, updated_at)
102
- VALUES (?, ?, '', '', '', ?, 'private', NULL, ?, ?)`,
103
- ['task-2', 'token-2', 'session-2', now, now]
104
- )
105
- run(
106
- `INSERT INTO codex_sessions (id, title, engine, cwd, codex_thread_id, engine_session_id, engine_thread_id, engine_meta_json, created_at, updated_at)
107
- VALUES (?, ?, 'codex', ?, '', '', '', '{}', ?, ?)`,
108
- ['session-2', 'Session 2', sharedTempDir, now, now]
109
- )
110
- run(
111
- `INSERT INTO codex_runs (id, task_slug, session_id, engine, prompt, prompt_blocks_json, status, response_message, error_message, created_at, updated_at, started_at, finished_at)
112
- VALUES (?, ?, ?, 'codex', ?, '[]', 'queued', '', '', ?, ?, NULL, NULL)`,
113
- ['run-2', 'task-2', 'session-2', 'hello', now, now]
114
- )
115
-
116
- const reconciled = await reconcileRunAfterRunnerDispatchError({
117
- runId: 'run-2',
118
- error: Object.assign(new Error('runner request timed out after 5000ms'), { statusCode: 504 }),
119
- runnerClient: {
120
- async getRun() {
121
- const lookupError = new Error('runner lookup failed')
122
- lookupError.statusCode = 503
123
- throw lookupError
124
- },
125
- },
126
- fallbackStatus: 'queued',
127
- logger: {
128
- warn() {},
129
- },
130
- })
131
-
132
- assert.equal(reconciled.pending, true)
133
- assert.equal(reconciled.syncedFromRunner, false)
134
- assert.equal(reconciled.run?.status, 'queued')
135
- assert.equal(getCodexRunById('run-2')?.status, 'queued')
136
- })
@@ -1,112 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import fs from 'node:fs'
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import test from 'node:test'
6
-
7
- test('system config module reads env override and stored values', async () => {
8
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-system-config-'))
9
- const originalDataDir = process.env.PROMPTX_DATA_DIR
10
- const originalRunnerConcurrency = process.env.PROMPTX_RUNNER_MAX_CONCURRENT_RUNS
11
- process.env.PROMPTX_DATA_DIR = tempDir
12
-
13
- try {
14
- const module = await import(`./systemConfig.js?test=${Date.now()}`)
15
- const saved = module.writeStoredSystemConfig({
16
- runner: {
17
- maxConcurrentRuns: 3,
18
- },
19
- })
20
-
21
- assert.equal(saved.runner.maxConcurrentRuns, 3)
22
- assert.equal(fs.existsSync(module.getSystemConfigPath()), true)
23
- assert.equal(module.readStoredSystemConfig().runner.maxConcurrentRuns, 3)
24
- assert.equal(module.getSystemConfigManagedByEnv().runner.maxConcurrentRuns, false)
25
- assert.equal(module.getSystemConfigForClient().runner.maxConcurrentRuns, 3)
26
-
27
- process.env.PROMPTX_RUNNER_MAX_CONCURRENT_RUNS = '5'
28
- const moduleWithEnv = await import(`./systemConfig.js?test=${Date.now()}-env`)
29
- assert.equal(moduleWithEnv.getSystemConfigManagedByEnv().runner.maxConcurrentRuns, true)
30
- assert.equal(moduleWithEnv.getSystemConfigForClient().runner.maxConcurrentRuns, 5)
31
- } finally {
32
- if (typeof originalDataDir === 'string') {
33
- process.env.PROMPTX_DATA_DIR = originalDataDir
34
- } else {
35
- delete process.env.PROMPTX_DATA_DIR
36
- }
37
-
38
- if (typeof originalRunnerConcurrency === 'string') {
39
- process.env.PROMPTX_RUNNER_MAX_CONCURRENT_RUNS = originalRunnerConcurrency
40
- } else {
41
- delete process.env.PROMPTX_RUNNER_MAX_CONCURRENT_RUNS
42
- }
43
- }
44
- })
45
-
46
- test('system config module redacts trusted proxy token for client payloads', async () => {
47
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-system-config-'))
48
- const originalDataDir = process.env.PROMPTX_DATA_DIR
49
- process.env.PROMPTX_DATA_DIR = tempDir
50
-
51
- try {
52
- const module = await import(`./systemConfig.js?test=${Date.now()}-redact`)
53
- module.writeStoredSystemConfig({
54
- runner: {
55
- maxConcurrentRuns: 3,
56
- },
57
- remoteCommandSecurity: {
58
- mode: 'trusted-proxy',
59
- trustedProxyToken: 'trusted-token',
60
- },
61
- })
62
-
63
- assert.equal(module.getSystemConfigForRuntime().remoteCommandSecurity.trustedProxyToken, 'trusted-token')
64
- assert.deepEqual(module.getSystemConfigForClient().remoteCommandSecurity, {
65
- mode: 'trusted-proxy',
66
- trustedProxyTokenConfigured: true,
67
- })
68
- } finally {
69
- if (typeof originalDataDir === 'string') {
70
- process.env.PROMPTX_DATA_DIR = originalDataDir
71
- } else {
72
- delete process.env.PROMPTX_DATA_DIR
73
- }
74
- }
75
- })
76
-
77
- test('system config module clears trusted proxy token when mode is not trusted-proxy', async () => {
78
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-system-config-'))
79
- const originalDataDir = process.env.PROMPTX_DATA_DIR
80
- process.env.PROMPTX_DATA_DIR = tempDir
81
-
82
- try {
83
- const module = await import(`./systemConfig.js?test=${Date.now()}-clear-token`)
84
- module.writeStoredSystemConfig({
85
- remoteCommandSecurity: {
86
- mode: 'trusted-proxy',
87
- trustedProxyToken: 'trusted-token',
88
- },
89
- })
90
-
91
- const saved = module.writeStoredSystemConfig({
92
- remoteCommandSecurity: {
93
- mode: 'relay',
94
- },
95
- })
96
-
97
- assert.deepEqual(saved.remoteCommandSecurity, {
98
- mode: 'relay',
99
- trustedProxyToken: '',
100
- })
101
- assert.deepEqual(module.readStoredSystemConfig().remoteCommandSecurity, {
102
- mode: 'relay',
103
- trustedProxyToken: '',
104
- })
105
- } finally {
106
- if (typeof originalDataDir === 'string') {
107
- process.env.PROMPTX_DATA_DIR = originalDataDir
108
- } else {
109
- delete process.env.PROMPTX_DATA_DIR
110
- }
111
- }
112
- })