@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,93 +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 { createServerClient } from './serverClient.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('serverClient attaches internal auth header and posts JSON payload', async () => {
22
- const { server, baseUrl } = await startJsonServer(async (request, response) => {
23
- assert.equal(request.headers[getInternalAuthHeaderName()], getInternalAuthToken())
24
-
25
- const chunks = []
26
- for await (const chunk of request) {
27
- chunks.push(chunk)
28
- }
29
- const body = JSON.parse(Buffer.concat(chunks).toString('utf8'))
30
- assert.equal(body.items.length, 1)
31
-
32
- response.writeHead(200, { 'Content-Type': 'application/json' })
33
- response.end(JSON.stringify({ ok: true }))
34
- })
35
-
36
- try {
37
- const client = createServerClient({ baseUrl, timeoutMs: 1000 })
38
- const payload = await client.postEvents([{ id: 1 }], { runnerId: 'runner-1' })
39
- assert.deepEqual(payload, { ok: true })
40
- } finally {
41
- await new Promise((resolve) => server.close(resolve))
42
- }
43
- })
44
-
45
- test('serverClient fails fast on timeout', async () => {
46
- const { server, baseUrl } = await startJsonServer((_request, response) => {
47
- setTimeout(() => {
48
- response.writeHead(200, { 'Content-Type': 'application/json' })
49
- response.end(JSON.stringify({ ok: true }))
50
- }, 700)
51
- })
52
-
53
- try {
54
- const client = createServerClient({ baseUrl, timeoutMs: 500 })
55
- await assert.rejects(
56
- () => client.postStatus({ ok: true }),
57
- (error) => error?.statusCode === 504 && /超时/.test(String(error?.message || ''))
58
- )
59
- } finally {
60
- await new Promise((resolve) => server.close(resolve))
61
- }
62
- })
63
-
64
- test('serverClient reads system config with internal auth header', async () => {
65
- const { server, baseUrl } = await startJsonServer((request, response) => {
66
- assert.equal(request.method, 'GET')
67
- assert.equal(request.url, '/internal/system-config')
68
- assert.equal(request.headers[getInternalAuthHeaderName()], getInternalAuthToken())
69
-
70
- response.writeHead(200, { 'Content-Type': 'application/json' })
71
- response.end(JSON.stringify({
72
- config: {
73
- runner: {
74
- maxConcurrentRuns: 4,
75
- },
76
- },
77
- }))
78
- })
79
-
80
- try {
81
- const client = createServerClient({ baseUrl, timeoutMs: 1000 })
82
- const payload = await client.getSystemConfig()
83
- assert.deepEqual(payload, {
84
- config: {
85
- runner: {
86
- maxConcurrentRuns: 4,
87
- },
88
- },
89
- })
90
- } finally {
91
- await new Promise((resolve) => server.close(resolve))
92
- }
93
- })
@@ -1,186 +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
- import {
8
- decodeClaudeProjectPath,
9
- listKnownClaudeCodeSessions,
10
- listKnownKimiCodeSessions,
11
- listKnownOpenCodeSessions,
12
- } from './agentSessionDiscovery.js'
13
-
14
- test('decodeClaudeProjectPath decodes unix-style project keys', () => {
15
- assert.equal(
16
- decodeClaudeProjectPath('-Users-bravf-code-promptx'),
17
- '/Users/bravf/code/promptx'
18
- )
19
- })
20
-
21
- test('listKnownClaudeCodeSessions merges transcripts and project files', () => {
22
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-claude-discovery-'))
23
- const claudeHome = path.join(tempRoot, '.claude')
24
- const transcriptDir = path.join(claudeHome, 'transcripts')
25
- const projectsDir = path.join(claudeHome, 'projects', '-Users-bravf-code-promptx')
26
-
27
- fs.mkdirSync(transcriptDir, { recursive: true })
28
- fs.mkdirSync(projectsDir, { recursive: true })
29
-
30
- const transcriptPath = path.join(transcriptDir, 'ses_transcript_only.jsonl')
31
- const projectPath = path.join(projectsDir, 'ses_project.jsonl')
32
-
33
- fs.writeFileSync(transcriptPath, `${JSON.stringify({ type: 'user', message: { text: '请继续修复 PromptX' } })}\n`)
34
- fs.writeFileSync(projectPath, `${JSON.stringify({ type: 'user', message: { text: '帮我看下项目源码' } })}\n`)
35
-
36
- const now = new Date('2026-04-13T08:00:00.000Z')
37
- fs.utimesSync(transcriptPath, now, now)
38
- fs.utimesSync(projectPath, new Date(now.getTime() + 1000), new Date(now.getTime() + 1000))
39
-
40
- try {
41
- const items = listKnownClaudeCodeSessions({
42
- claudeHome,
43
- limit: 10,
44
- cwd: '/Users/bravf/code/promptx',
45
- })
46
-
47
- assert.equal(items.length, 2)
48
- assert.deepEqual(
49
- items.map((item) => ({
50
- id: item.id,
51
- cwd: item.cwd,
52
- matchedCwd: item.matchedCwd,
53
- })),
54
- [
55
- {
56
- id: 'ses_project',
57
- cwd: '/Users/bravf/code/promptx',
58
- matchedCwd: true,
59
- },
60
- {
61
- id: 'ses_transcript_only',
62
- cwd: '',
63
- matchedCwd: false,
64
- },
65
- ]
66
- )
67
- assert.match(items[0].label, /项目源码|promptx/i)
68
- } finally {
69
- fs.rmSync(tempRoot, { recursive: true, force: true })
70
- }
71
- })
72
-
73
- test('listKnownOpenCodeSessions discovers sessions from desktop dat files', () => {
74
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-opencode-discovery-'))
75
- const dataDir = path.join(tempRoot, 'ai.opencode.desktop')
76
- fs.mkdirSync(dataDir, { recursive: true })
77
-
78
- const globalPath = path.join(dataDir, 'opencode.global.dat')
79
- const workspacePath = path.join(
80
- dataDir,
81
- `opencode.workspace.${Buffer.from('/Users/bravf/code/promptx').toString('base64')}.test.dat`
82
- )
83
-
84
- fs.writeFileSync(globalPath, JSON.stringify({
85
- 'layout.page': JSON.stringify({
86
- lastProjectSession: {
87
- '/Users/bravf/code/promptx': {
88
- directory: '/Users/bravf/code/promptx',
89
- id: 'ses_global',
90
- at: '2026-04-13T08:00:00.000Z',
91
- },
92
- },
93
- lastSession: {
94
- '/Users/bravf/code/promptx': 'ses_last',
95
- },
96
- }),
97
- }))
98
-
99
- fs.writeFileSync(workspacePath, JSON.stringify({
100
- 'session:ses_global:comments': '[]',
101
- 'session:ses_workspace:prompt': '请继续处理前端主题',
102
- }))
103
-
104
- try {
105
- const items = listKnownOpenCodeSessions({
106
- openCodeDataDir: dataDir,
107
- openCodeDbPath: path.join(tempRoot, 'missing-opencode.db'),
108
- cwd: '/Users/bravf/code/promptx',
109
- limit: 10,
110
- })
111
-
112
- assert.deepEqual(
113
- items.map((item) => ({
114
- id: item.id,
115
- cwd: item.cwd,
116
- matchedCwd: item.matchedCwd,
117
- })),
118
- [
119
- { id: 'ses_global', cwd: '/Users/bravf/code/promptx', matchedCwd: true },
120
- { id: 'ses_workspace', cwd: '/Users/bravf/code/promptx', matchedCwd: true },
121
- { id: 'ses_last', cwd: '/Users/bravf/code/promptx', matchedCwd: true },
122
- ]
123
- )
124
- assert.match(items[1].label, /前端主题/)
125
- } finally {
126
- fs.rmSync(tempRoot, { recursive: true, force: true })
127
- }
128
- })
129
-
130
- test('listKnownKimiCodeSessions merges kimi.json work dirs with session state', () => {
131
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-kimi-discovery-'))
132
- const kimiHome = path.join(tempRoot, '.kimi')
133
- const sessionDir = path.join(kimiHome, 'sessions', 'hash-1', 'kimi-session-1')
134
- fs.mkdirSync(sessionDir, { recursive: true })
135
-
136
- const kimiJsonPath = path.join(kimiHome, 'kimi.json')
137
- const statePath = path.join(sessionDir, 'state.json')
138
- const contextPath = path.join(sessionDir, 'context.jsonl')
139
- const kimiJsonTime = new Date('2026-04-13T08:00:00.000Z')
140
- const stateTime = new Date('2026-04-13T09:00:00.000Z')
141
-
142
- fs.writeFileSync(kimiJsonPath, JSON.stringify({
143
- work_dirs: [
144
- {
145
- path: '/Users/bravf/code/promptx',
146
- last_session_id: 'kimi-session-1',
147
- },
148
- ],
149
- }))
150
- fs.writeFileSync(statePath, JSON.stringify({
151
- custom_title: '继续 Kimi 项目',
152
- }))
153
- fs.writeFileSync(contextPath, `${JSON.stringify({ role: 'user', content: '帮我修复 Kimi 接入' })}\n`)
154
- fs.utimesSync(kimiJsonPath, kimiJsonTime, kimiJsonTime)
155
- fs.utimesSync(statePath, stateTime, stateTime)
156
-
157
- try {
158
- const items = listKnownKimiCodeSessions({
159
- kimiHome,
160
- limit: 10,
161
- cwd: '/Users/bravf/code/promptx',
162
- })
163
-
164
- assert.equal(items.length, 1)
165
- assert.deepEqual(
166
- {
167
- id: items[0].id,
168
- label: items[0].label,
169
- cwd: items[0].cwd,
170
- updatedAt: items[0].updatedAt,
171
- updatedAtSource: items[0].updatedAtSource,
172
- matchedCwd: items[0].matchedCwd,
173
- },
174
- {
175
- id: 'kimi-session-1',
176
- label: '继续 Kimi 项目',
177
- cwd: '/Users/bravf/code/promptx',
178
- updatedAt: stateTime.toISOString(),
179
- updatedAtSource: 'explicit',
180
- matchedCwd: true,
181
- }
182
- )
183
- } finally {
184
- fs.rmSync(tempRoot, { recursive: true, force: true })
185
- }
186
- })
@@ -1,52 +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
- function withEnv(overrides, fn) {
8
- const previous = new Map()
9
-
10
- for (const [key, value] of Object.entries(overrides)) {
11
- previous.set(key, process.env[key])
12
- if (typeof value === 'undefined') {
13
- delete process.env[key]
14
- } else {
15
- process.env[key] = value
16
- }
17
- }
18
-
19
- return Promise.resolve()
20
- .then(fn)
21
- .finally(() => {
22
- for (const [key, value] of previous.entries()) {
23
- if (typeof value === 'undefined') {
24
- delete process.env[key]
25
- } else {
26
- process.env[key] = value
27
- }
28
- }
29
- })
30
- }
31
-
32
- test('appPaths resolves storage under PROMPTX_HOME by default', async () => {
33
- const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-home-'))
34
-
35
- await withEnv(
36
- {
37
- PROMPTX_HOME: tempHome,
38
- PROMPTX_DATA_DIR: undefined,
39
- PROMPTX_UPLOADS_DIR: undefined,
40
- PROMPTX_TMP_DIR: undefined,
41
- },
42
- async () => {
43
- const appPaths = await import(`./appPaths.js?test=${Date.now()}`)
44
- const resolved = appPaths.ensurePromptxStorageReady()
45
-
46
- assert.equal(resolved.promptxHomeDir, tempHome)
47
- assert.equal(resolved.dataDir, path.join(tempHome, 'data'))
48
- assert.equal(resolved.uploadsDir, path.join(tempHome, 'uploads'))
49
- assert.equal(resolved.tmpDir, path.join(tempHome, 'tmp'))
50
- }
51
- )
52
- })
@@ -1,168 +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 Fastify from 'fastify'
6
- import multipart from '@fastify/multipart'
7
- import test from 'node:test'
8
- import { createCanvas } from '@napi-rs/canvas'
9
-
10
- import { registerAssetRoutes } from './assetRoutes.js'
11
-
12
- function createMultipartBody(fileName, mimeType, buffer) {
13
- const boundary = `----promptx-test-${Date.now()}`
14
- const head = Buffer.from(
15
- `--${boundary}\r\n`
16
- + `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n`
17
- + `Content-Type: ${mimeType}\r\n\r\n`
18
- )
19
- const tail = Buffer.from(`\r\n--${boundary}--\r\n`)
20
-
21
- return {
22
- boundary,
23
- body: Buffer.concat([head, buffer, tail]),
24
- }
25
- }
26
-
27
- function createTestPngBuffer(width = 2000, height = 1000) {
28
- const canvas = createCanvas(width, height)
29
- const context = canvas.getContext('2d')
30
- context.fillStyle = '#0ea5e9'
31
- context.fillRect(0, 0, width, height)
32
- return canvas.toBuffer('image/png')
33
- }
34
-
35
- function createTestSvgBuffer(width = 120, height = 80) {
36
- return Buffer.from(
37
- `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`
38
- + '<rect width="100%" height="100%" fill="#0ea5e9"/>'
39
- + '</svg>',
40
- 'utf8'
41
- )
42
- }
43
-
44
- test('asset routes reject missing upload file', async () => {
45
- const app = Fastify()
46
- await app.register(multipart)
47
- registerAssetRoutes(app, {
48
- createTempFilePath: () => '',
49
- importPdfBlocks: async () => ({ blocks: [] }),
50
- normalizeUploadFileName: (name) => name,
51
- removeAssetFiles: () => {},
52
- tmpDir: 'tmp',
53
- uploadsDir: 'uploads',
54
- })
55
- await app.ready()
56
-
57
- try {
58
- const uploadResponse = await app.inject({
59
- method: 'POST',
60
- url: '/api/uploads',
61
- })
62
- assert.equal(uploadResponse.statusCode, 406)
63
-
64
- const pdfResponse = await app.inject({
65
- method: 'POST',
66
- url: '/api/imports/pdf',
67
- })
68
- assert.equal(pdfResponse.statusCode, 406)
69
- } finally {
70
- await app.close()
71
- }
72
- })
73
-
74
- test('asset routes keep uploaded png format and original bytes', async () => {
75
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-asset-routes-'))
76
- const uploadsDir = path.join(rootDir, 'uploads')
77
- const tmpDir = path.join(rootDir, 'tmp')
78
- fs.mkdirSync(uploadsDir, { recursive: true })
79
- fs.mkdirSync(tmpDir, { recursive: true })
80
-
81
- const app = Fastify()
82
- await app.register(multipart)
83
- registerAssetRoutes(app, {
84
- createTempFilePath: (_dir, fileName) => path.join(tmpDir, fileName),
85
- importPdfBlocks: async () => ({ blocks: [] }),
86
- normalizeUploadFileName: (name) => name,
87
- removeAssetFiles: () => {},
88
- tmpDir,
89
- uploadsDir,
90
- })
91
- await app.ready()
92
-
93
- try {
94
- const sourceBuffer = createTestPngBuffer()
95
- const payload = createMultipartBody('demo.png', 'image/png', sourceBuffer)
96
- const response = await app.inject({
97
- method: 'POST',
98
- url: '/api/uploads',
99
- headers: {
100
- 'content-type': `multipart/form-data; boundary=${payload.boundary}`,
101
- },
102
- payload: payload.body,
103
- })
104
-
105
- assert.equal(response.statusCode, 201)
106
- const body = response.json()
107
- assert.match(body.url || '', /^\/uploads\/.+\.png$/)
108
- assert.equal(body.width, 2000)
109
- assert.equal(body.height, 1000)
110
- assert.equal(body.mimeType, 'image/png')
111
- assert.equal(typeof body.size, 'number')
112
- assert.ok(body.size > 0)
113
-
114
- const outputPath = path.join(uploadsDir, path.basename(body.url))
115
- assert.equal(fs.existsSync(outputPath), true)
116
- assert.deepEqual(fs.readFileSync(outputPath), sourceBuffer)
117
- } finally {
118
- await app.close()
119
- fs.rmSync(rootDir, { recursive: true, force: true })
120
- }
121
- })
122
-
123
- test('asset routes keep uploaded svg format and original bytes', async () => {
124
- const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-asset-routes-svg-'))
125
- const uploadsDir = path.join(rootDir, 'uploads')
126
- const tmpDir = path.join(rootDir, 'tmp')
127
- fs.mkdirSync(uploadsDir, { recursive: true })
128
- fs.mkdirSync(tmpDir, { recursive: true })
129
-
130
- const app = Fastify()
131
- await app.register(multipart)
132
- registerAssetRoutes(app, {
133
- createTempFilePath: (_dir, fileName) => path.join(tmpDir, fileName),
134
- importPdfBlocks: async () => ({ blocks: [] }),
135
- normalizeUploadFileName: (name) => name,
136
- removeAssetFiles: () => {},
137
- tmpDir,
138
- uploadsDir,
139
- })
140
- await app.ready()
141
-
142
- try {
143
- const sourceBuffer = createTestSvgBuffer()
144
- const payload = createMultipartBody('logo.svg', 'image/svg+xml', sourceBuffer)
145
- const response = await app.inject({
146
- method: 'POST',
147
- url: '/api/uploads',
148
- headers: {
149
- 'content-type': `multipart/form-data; boundary=${payload.boundary}`,
150
- },
151
- payload: payload.body,
152
- })
153
-
154
- assert.equal(response.statusCode, 201)
155
- const body = response.json()
156
- assert.match(body.url || '', /^\/uploads\/.+\.svg$/)
157
- assert.equal(body.mimeType, 'image/svg+xml')
158
- assert.equal(typeof body.size, 'number')
159
- assert.ok(body.size > 0)
160
-
161
- const outputPath = path.join(uploadsDir, path.basename(body.url))
162
- assert.equal(fs.existsSync(outputPath), true)
163
- assert.deepEqual(fs.readFileSync(outputPath), sourceBuffer)
164
- } finally {
165
- await app.close()
166
- fs.rmSync(rootDir, { recursive: true, force: true })
167
- }
168
- })