@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,134 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import Fastify from 'fastify'
3
- import test from 'node:test'
4
-
5
- import { buildInternalAuthHeaders } from './internalAuth.js'
6
- import {
7
- registerInternalRunnerRoutes,
8
- registerRealtimeRoutes,
9
- } from './internalRoutes.js'
10
-
11
- test('internal runner routes require auth and notify completed runs', async () => {
12
- const events = []
13
- const notified = []
14
- const app = Fastify()
15
-
16
- registerInternalRunnerRoutes(app, {
17
- runEventIngestService: {
18
- ingestEvents(items) {
19
- events.push(...items)
20
- return { ok: true, count: items.length }
21
- },
22
- ingestStatus(payload) {
23
- return {
24
- ...payload,
25
- id: payload.runId,
26
- completed: true,
27
- }
28
- },
29
- },
30
- taskAutomationService: {
31
- notifyRun(taskSlug, runId) {
32
- notified.push({ taskSlug, runId })
33
- return Promise.resolve()
34
- },
35
- },
36
- })
37
- await app.ready()
38
-
39
- try {
40
- const unauthorized = await app.inject({
41
- method: 'POST',
42
- url: '/internal/runner-events',
43
- payload: { items: [] },
44
- })
45
- assert.equal(unauthorized.statusCode, 401)
46
-
47
- const eventsResponse = await app.inject({
48
- method: 'POST',
49
- url: '/internal/runner-events',
50
- headers: buildInternalAuthHeaders(),
51
- payload: {
52
- items: [{ runId: 'run-1', type: 'stdout' }],
53
- },
54
- })
55
- assert.equal(eventsResponse.statusCode, 200)
56
- assert.equal(events.length, 1)
57
-
58
- const statusResponse = await app.inject({
59
- method: 'POST',
60
- url: '/internal/runner-status',
61
- headers: buildInternalAuthHeaders(),
62
- payload: {
63
- runId: 'run-1',
64
- taskSlug: 'task-1',
65
- status: 'completed',
66
- },
67
- })
68
- assert.equal(statusResponse.statusCode, 200)
69
- assert.deepEqual(notified, [{ taskSlug: 'task-1', runId: 'run-1' }])
70
- } finally {
71
- await app.close()
72
- }
73
- })
74
-
75
- test('internal runner events route accepts payloads larger than default body limit', async () => {
76
- const app = Fastify()
77
- let acceptedCount = 0
78
-
79
- registerInternalRunnerRoutes(app, {
80
- runEventIngestService: {
81
- ingestEvents(items) {
82
- acceptedCount = items.length
83
- return { ok: true, count: items.length }
84
- },
85
- ingestStatus() {
86
- return null
87
- },
88
- },
89
- taskAutomationService: {
90
- notifyRun() {
91
- return Promise.resolve()
92
- },
93
- },
94
- })
95
- await app.ready()
96
-
97
- try {
98
- const largeText = 'x'.repeat(2 * 1024 * 1024)
99
- const response = await app.inject({
100
- method: 'POST',
101
- url: '/internal/runner-events',
102
- headers: buildInternalAuthHeaders(),
103
- payload: {
104
- items: [{ runId: 'run-large', seq: 1, type: 'stdout', payload: { text: largeText } }],
105
- },
106
- })
107
-
108
- assert.equal(response.statusCode, 200)
109
- assert.equal(acceptedCount, 1)
110
- } finally {
111
- await app.close()
112
- }
113
- })
114
-
115
- test('realtime routes are registered on the app', async () => {
116
- const app = Fastify()
117
-
118
- registerRealtimeRoutes(app, {
119
- sseHub: {
120
- addClient() {
121
- return () => {}
122
- },
123
- write() {},
124
- },
125
- })
126
- await app.ready()
127
-
128
- try {
129
- const routes = app.printRoutes()
130
- assert.match(routes, /api\/events\/stream/)
131
- } finally {
132
- await app.close()
133
- }
134
- })
@@ -1,154 +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, { after } from 'node:test'
6
-
7
- const sharedTempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-maintenance-suite-'))
8
- const sharedDataDir = path.join(sharedTempDir, 'data')
9
- const originalDataDir = process.env.PROMPTX_DATA_DIR
10
- fs.mkdirSync(sharedDataDir, { recursive: true })
11
- process.env.PROMPTX_DATA_DIR = sharedDataDir
12
-
13
- let closeDatabaseForTesting = () => {}
14
-
15
- after(() => {
16
- closeDatabaseForTesting()
17
- if (typeof originalDataDir === 'string') {
18
- process.env.PROMPTX_DATA_DIR = originalDataDir
19
- } else {
20
- delete process.env.PROMPTX_DATA_DIR
21
- }
22
- fs.rmSync(sharedTempDir, { recursive: true, force: true })
23
- })
24
-
25
- test('maintenance service prunes stale tmp files and runner check directories', async () => {
26
- const tempDir = path.join(sharedTempDir, 'fs-cleanup')
27
- const tmpDir = path.join(tempDir, 'tmp')
28
- const reportDir = path.join(tempDir, 'reports', 'runner-checks')
29
- fs.mkdirSync(tmpDir, { recursive: true })
30
- fs.mkdirSync(reportDir, { recursive: true })
31
-
32
- const staleTmpFile = path.join(tmpDir, 'stale.tmp')
33
- const freshTmpFile = path.join(tmpDir, 'fresh.tmp')
34
- const staleReportDir = path.join(reportDir, 'old-report')
35
- const freshReportDir = path.join(reportDir, 'new-report')
36
-
37
- fs.writeFileSync(staleTmpFile, 'stale')
38
- fs.writeFileSync(freshTmpFile, 'fresh')
39
- fs.mkdirSync(staleReportDir, { recursive: true })
40
- fs.mkdirSync(freshReportDir, { recursive: true })
41
-
42
- const staleTime = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000)
43
- fs.utimesSync(staleTmpFile, staleTime, staleTime)
44
- fs.utimesSync(staleReportDir, staleTime, staleTime)
45
-
46
- const { createMaintenanceService } = await import(`./maintenance.js?test=${Date.now()}`)
47
- const plainDbModule = await import('./db.js')
48
- closeDatabaseForTesting = plainDbModule.closeDatabaseForTesting
49
- const service = createMaintenanceService({
50
- tmpDir,
51
- cleanupIntervalMs: 60_000,
52
- reportRetentionMs: 24 * 60 * 60 * 1000,
53
- tmpFileRetentionMs: 24 * 60 * 60 * 1000,
54
- reportDirs: [reportDir],
55
- runEventRetentionMs: 365 * 24 * 60 * 60 * 1000,
56
- })
57
-
58
- const diagnosticsBefore = service.getDiagnostics()
59
- assert.equal(diagnosticsBefore.lastCleanup.startedAt, '')
60
-
61
- const result = service.runCleanup()
62
-
63
- assert.equal(fs.existsSync(staleTmpFile), false)
64
- assert.equal(fs.existsSync(freshTmpFile), true)
65
- assert.equal(fs.existsSync(staleReportDir), false)
66
- assert.equal(fs.existsSync(freshReportDir), true)
67
- assert.equal(result.removedTmpFiles >= 1, true)
68
- })
69
-
70
- test('maintenance service prunes stale run events and runs sqlite maintenance', async () => {
71
- const dbModule = await import('./db.js')
72
- const { run } = dbModule
73
- const { listCodexRunEvents } = await import('./codexRuns.js')
74
- const { createMaintenanceService } = await import(`./maintenance.js?test-db=${Date.now()}`)
75
-
76
- const staleFinishedAt = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString()
77
- const recentFinishedAt = new Date().toISOString()
78
-
79
- run(
80
- `INSERT INTO tasks (slug, edit_token, title, auto_title, last_prompt_preview, codex_session_id, visibility, expires_at, created_at, updated_at)
81
- VALUES (?, ?, '', '', '', ?, 'private', NULL, ?, ?)`,
82
- ['task-maint-1', 'token-1', 'session-maint-1', staleFinishedAt, staleFinishedAt]
83
- )
84
- run(
85
- `INSERT INTO codex_sessions (id, title, engine, cwd, codex_thread_id, engine_session_id, engine_thread_id, engine_meta_json, created_at, updated_at)
86
- VALUES (?, ?, 'codex', ?, '', '', '', '{}', ?, ?)`,
87
- ['session-maint-1', 'Session Maint 1', sharedTempDir, staleFinishedAt, staleFinishedAt]
88
- )
89
- run(
90
- `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)
91
- VALUES (?, ?, ?, 'codex', ?, '[]', 'completed', '', '', ?, ?, ?, ?)`,
92
- ['run-stale', 'task-maint-1', 'session-maint-1', 'old', staleFinishedAt, staleFinishedAt, staleFinishedAt, staleFinishedAt]
93
- )
94
- run(
95
- `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)
96
- VALUES (?, ?, ?, 'codex', ?, '[]', 'completed', '', '', ?, ?, ?, ?)`,
97
- ['run-capped', 'task-maint-1', 'session-maint-1', 'new', recentFinishedAt, recentFinishedAt, recentFinishedAt, recentFinishedAt]
98
- )
99
- run(
100
- `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)
101
- VALUES (?, ?, ?, 'codex', ?, '[]', 'running', '', '', ?, ?, ?, NULL)`,
102
- ['run-active', 'task-maint-1', 'session-maint-1', 'active', recentFinishedAt, recentFinishedAt, recentFinishedAt]
103
- )
104
-
105
- for (let seq = 1; seq <= 3; seq += 1) {
106
- run(
107
- `INSERT INTO codex_run_events (run_id, seq, event_type, payload_json, created_at)
108
- VALUES (?, ?, 'event', '{}', ?)`,
109
- ['run-stale', seq, staleFinishedAt]
110
- )
111
- }
112
- for (let seq = 1; seq <= 5; seq += 1) {
113
- run(
114
- `INSERT INTO codex_run_events (run_id, seq, event_type, payload_json, created_at)
115
- VALUES (?, ?, 'event', '{}', ?)`,
116
- ['run-capped', seq, recentFinishedAt]
117
- )
118
- }
119
- for (let seq = 1; seq <= 5; seq += 1) {
120
- run(
121
- `INSERT INTO codex_run_events (run_id, seq, event_type, payload_json, created_at)
122
- VALUES (?, ?, 'event', '{}', ?)`,
123
- ['run-active', seq, recentFinishedAt]
124
- )
125
- }
126
-
127
- const service = createMaintenanceService({
128
- tmpDir: path.join(sharedTempDir, 'tmp-db-cleanup'),
129
- cleanupIntervalMs: 60_000,
130
- runEventRetentionMs: 24 * 60 * 60 * 1000,
131
- maxRunEventsPerRun: 2,
132
- dbVacuumIntervalMs: 60_000,
133
- })
134
-
135
- const result = service.runCleanup({ forceDbVacuum: true })
136
-
137
- assert.equal(result.runEvents.removedByRetention, 3)
138
- assert.equal(result.runEvents.removedByCount, 3)
139
- assert.equal(result.runEvents.removedTotal, 6)
140
- assert.equal(result.dbMaintenance.vacuumed, true)
141
- assert.equal(listCodexRunEvents('run-stale')?.length, 0)
142
- assert.deepEqual(
143
- (listCodexRunEvents('run-capped') || []).map((item) => item.seq),
144
- [4, 5]
145
- )
146
- assert.equal(listCodexRunEvents('run-active')?.length, 5)
147
-
148
- const diagnostics = service.getDiagnostics()
149
- assert.equal(diagnostics.runEventRetentionMs, 24 * 60 * 60 * 1000)
150
- assert.equal(diagnostics.maxRunEventsPerRun, 2)
151
- assert.equal(diagnostics.lastCleanup.runEvents.removedTotal, 6)
152
- assert.equal(diagnostics.lastCleanup.dbMaintenance.vacuumed, true)
153
- assert.ok(diagnostics.db.dbPath.endsWith('promptx.sqlite'))
154
- })
@@ -1,147 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { spawn } from 'node:child_process'
3
- import fs from 'node:fs'
4
- import os from 'node:os'
5
- import path from 'node:path'
6
- import test from 'node:test'
7
-
8
- import { createManagedSpawnOptions, forceStopChildProcess } from './processControl.js'
9
-
10
- function isProcessAlive(pid) {
11
- try {
12
- process.kill(pid, 0)
13
- return true
14
- } catch {
15
- return false
16
- }
17
- }
18
-
19
- async function waitFor(check, timeoutMs, errorMessage) {
20
- const startedAt = Date.now()
21
- while (Date.now() - startedAt < timeoutMs) {
22
- if (await check()) {
23
- return
24
- }
25
- await new Promise((resolve) => setTimeout(resolve, 50))
26
- }
27
-
28
- throw new Error(errorMessage)
29
- }
30
-
31
- test('forceStopChildProcess 会在宽限时间后结束忽略 SIGTERM 的子进程', async () => {
32
- const child = spawn(
33
- process.execPath,
34
- ['-e', 'process.on("SIGTERM", () => {}); setInterval(() => {}, 1000)'],
35
- createManagedSpawnOptions({
36
- stdio: ['ignore', 'ignore', 'ignore'],
37
- })
38
- )
39
-
40
- await new Promise((resolve) => {
41
- child.once('spawn', resolve)
42
- })
43
-
44
- assert.equal(typeof child.pid, 'number')
45
- forceStopChildProcess(child, { graceMs: 100 })
46
-
47
- const closeResult = await new Promise((resolve, reject) => {
48
- const timeout = setTimeout(() => {
49
- reject(new Error('等待子进程退出超时'))
50
- }, 5000)
51
-
52
- child.once('close', (code, signal) => {
53
- clearTimeout(timeout)
54
- resolve({ code, signal })
55
- })
56
- })
57
-
58
- assert.ok(closeResult.signal || closeResult.code !== null)
59
- })
60
-
61
- test('forceStopChildProcess 会结束脱离原进程组的后代进程', {
62
- skip: process.platform === 'win32',
63
- }, async () => {
64
- const fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-process-tree-'))
65
- const detachedPidFile = path.join(fixtureDir, 'detached.pid')
66
- const detachedExitFile = path.join(fixtureDir, 'detached.exit.json')
67
- let detachedPid = 0
68
- const detachedScript = [
69
- "const fs = require('node:fs')",
70
- `const detachedExitFile = ${JSON.stringify(detachedExitFile)}`,
71
- "process.on('SIGTERM', () => {",
72
- " fs.writeFileSync(detachedExitFile, JSON.stringify({ reason: 'sigterm' }))",
73
- " process.exit(0)",
74
- "})",
75
- "setInterval(() => {}, 1000)",
76
- ].join('\n')
77
-
78
- const parentScript = [
79
- "const fs = require('node:fs')",
80
- "const { spawn } = require('node:child_process')",
81
- `const detachedPidFile = ${JSON.stringify(detachedPidFile)}`,
82
- `const detachedScript = ${JSON.stringify(detachedScript)}`,
83
- "const detachedChild = spawn(process.execPath, ['-e', detachedScript], {",
84
- " detached: true,",
85
- " stdio: 'ignore',",
86
- "})",
87
- "fs.writeFileSync(detachedPidFile, String(detachedChild.pid))",
88
- "detachedChild.unref()",
89
- "process.on('SIGTERM', () => {})",
90
- "setInterval(() => {}, 1000)",
91
- ].join('\n')
92
-
93
- const child = spawn(
94
- process.execPath,
95
- ['-e', parentScript],
96
- createManagedSpawnOptions({
97
- stdio: ['ignore', 'ignore', 'ignore'],
98
- })
99
- )
100
-
101
- try {
102
- await new Promise((resolve) => {
103
- child.once('spawn', resolve)
104
- })
105
-
106
- await waitFor(() => fs.existsSync(detachedPidFile), 5000, '等待 detached.pid 超时')
107
- detachedPid = Number(fs.readFileSync(detachedPidFile, 'utf8').trim()) || 0
108
- assert.ok(detachedPid > 0)
109
- assert.equal(isProcessAlive(detachedPid), true)
110
-
111
- forceStopChildProcess(child, { graceMs: 100 })
112
-
113
- const closeResult = await new Promise((resolve, reject) => {
114
- const timeout = setTimeout(() => {
115
- reject(new Error('等待父进程退出超时'))
116
- }, 5000)
117
-
118
- child.once('close', (code, signal) => {
119
- clearTimeout(timeout)
120
- resolve({ code, signal })
121
- })
122
- })
123
-
124
- assert.ok(closeResult.signal || closeResult.code !== null)
125
-
126
- await waitFor(() => !isProcessAlive(detachedPid), 5000, '等待后代进程退出超时')
127
- await waitFor(() => fs.existsSync(detachedExitFile), 5000, '等待后代进程退出标记超时')
128
-
129
- const exitPayload = JSON.parse(fs.readFileSync(detachedExitFile, 'utf8'))
130
- assert.equal(exitPayload.reason, 'sigterm')
131
- } finally {
132
- if (detachedPid > 0 && isProcessAlive(detachedPid)) {
133
- try {
134
- process.kill(-detachedPid, 'SIGKILL')
135
- } catch {
136
- // Ignore cleanup failures for already-exited processes.
137
- }
138
- try {
139
- process.kill(detachedPid, 'SIGKILL')
140
- } catch {
141
- // Ignore cleanup failures for already-exited processes.
142
- }
143
- }
144
-
145
- fs.rmSync(fixtureDir, { recursive: true, force: true })
146
- }
147
- })