@muyichengshayu/promptx 0.1.4 → 0.1.6

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/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ ## 0.1.6
4
+
5
+ - 新增多租户 Relay 子域名接入能力,一个 Relay 进程即可同时服务多个同事的远程访问。
6
+ - 新增 `promptx relay tenant add/list/remove` 与 `promptx relay start/stop/restart/status`,补齐 Relay 的租户管理和后台运维命令。
7
+ - 完善 Relay 转发稳定性与诊断信息,修复远程请求体转发问题,并增加更清晰的 host、tenant 与拒绝原因日志。
8
+ - README 精简为入口说明,详细 Relay 部署与使用流程迁移到 `docs/relay-quickstart.md`。
9
+ - 修复 Windows 开发环境下 `pnpm dev` / `pnpm dev:tailscale` 可能报 `spawn EINVAL` 的问题。
10
+
11
+ ## 0.1.5
12
+
13
+ - 本轮提示词支持按新数据结构展示图片块,新的图文输入在会话区可直接看到附图预览。
14
+ - 修复旧库升级时 `prompt_blocks_json` 字段缺失导致服务启动报错的问题,补齐增量迁移。
15
+ - 优化项目管理交互:运行中也可打开“管理项目”,管理弹窗优先选中当前项目,编辑表单能正确回填。
16
+ - 简化项目选择器与项目列表状态展示,减少无意义抖动和过重高亮;新建任务按钮不再因列表加载短暂禁用。
17
+
18
+ ## 0.1.4
19
+
20
+ - 修复 Windows 下通过 npm 安装后的正式版在启动服务、发送请求和执行辅助命令时频繁弹出黑色控制台窗口的问题。
21
+ - 为 PromptX、Codex、Git 与发布检查相关的 Windows 子进程统一补充隐藏窗口选项,减少系统级弹窗干扰。
22
+
23
+ ## 0.1.3
24
+
25
+ - 修复工作台输入区在输入法候选、实时刷新和快速发送场景下的尾字丢失问题。
26
+ - 修复设置面板首次打开时版本信息不加载、一直停留在“读取中...”的问题。
27
+
28
+ ## 0.1.2
29
+
30
+ - 设置面板增加版本信息展示,并在读取失败时显示明确状态。
31
+ - CLI 新增 `version`、`--version`、`-v` 版本查询,同时兼容 `-versioin`。
32
+ - README 补充说明:禅道扩展目前需要下载或克隆仓库源码后手动加载。
33
+
34
+ ## 0.1.1
35
+
36
+ - 修复工作台中“代码变更”入口、执行过程“查看”按钮和任务卡文件数徽标的闪烁问题。
37
+ - 优化任务列表刷新时的代码变更摘要复用逻辑,减少界面抖动。
package/README.md CHANGED
@@ -36,12 +36,18 @@ promptx doctor
36
36
  promptx start
37
37
  promptx status
38
38
  promptx stop
39
+ promptx relay start
39
40
  ```
40
41
 
41
42
  ```bash
42
43
  promptx doctor
43
44
  ```
44
45
 
46
+ 其中:
47
+
48
+ - `promptx start`:启动本机 PromptX 工作台
49
+ - `promptx relay start`:启动公网中转服务,适合部署到你自己的云服务器
50
+
45
51
  ## 使用方式
46
52
 
47
53
  1. 打开工作台,新建或选择一个任务
@@ -50,6 +56,21 @@ promptx doctor
50
56
  4. 点击发送,把当前内容交给 Codex
51
57
  5. 在中间继续查看执行过程,并按需多轮发送
52
58
 
59
+ ## 远程访问 Relay(预览)
60
+
61
+ 如果你希望在手机上远程访问自己电脑上的 PromptX,或想在云端部署多租户 Relay,请直接查看:
62
+
63
+ - `docs/relay-quickstart.md`
64
+
65
+ 文档里已经整理好这些内容:
66
+
67
+ - 本地 PromptX 接入 Relay
68
+ - 云端 Relay 启动与后台管理
69
+ - 多租户子域名接入
70
+ - `promptx relay tenant add/list/remove`
71
+ - `promptx relay start/stop/restart/status`
72
+ - Nginx、DNS、健康检查与常见排查
73
+
53
74
  ## 禅道扩展
54
75
 
55
76
  仓库内置了禅道 Chrome 扩展:`apps/zentao-extension`
@@ -1,4 +1,5 @@
1
1
  import { nanoid } from 'nanoid'
2
+ import { BLOCK_TYPES, clampText } from '../../../packages/shared/src/index.js'
2
3
  import { all, get, run, transaction } from './db.js'
3
4
  import { getPromptxCodexSessionById } from './codexSessions.js'
4
5
  import { captureRunGitBaseline, captureRunGitFinalSnapshot, captureTaskGitBaseline } from './gitDiff.js'
@@ -18,6 +19,48 @@ function parseEventPayload(rawValue = '{}') {
18
19
  }
19
20
  }
20
21
 
22
+ function parsePromptBlocks(rawValue = '[]') {
23
+ try {
24
+ const parsed = JSON.parse(rawValue || '[]')
25
+ return Array.isArray(parsed) ? parsed.filter(Boolean) : []
26
+ } catch {
27
+ return []
28
+ }
29
+ }
30
+
31
+ function normalizePromptBlock(block = {}) {
32
+ const type =
33
+ block.type === BLOCK_TYPES.IMAGE
34
+ ? BLOCK_TYPES.IMAGE
35
+ : block.type === BLOCK_TYPES.IMPORTED_TEXT
36
+ ? BLOCK_TYPES.IMPORTED_TEXT
37
+ : BLOCK_TYPES.TEXT
38
+
39
+ const content = clampText(
40
+ String(block.content || ''),
41
+ type === BLOCK_TYPES.IMAGE ? 1000 : 50000
42
+ )
43
+ const meta =
44
+ type === BLOCK_TYPES.IMPORTED_TEXT
45
+ ? {
46
+ fileName: clampText(block.meta?.fileName || '', 180),
47
+ collapsed: Boolean(block.meta?.collapsed),
48
+ }
49
+ : {}
50
+
51
+ return {
52
+ type,
53
+ content,
54
+ meta,
55
+ }
56
+ }
57
+
58
+ function normalizePromptBlocks(blocks = []) {
59
+ return Array.isArray(blocks)
60
+ ? blocks.map((block) => normalizePromptBlock(block)).filter(Boolean)
61
+ : []
62
+ }
63
+
21
64
  function toCodexRunEvent(row) {
22
65
  return {
23
66
  id: Number(row.id),
@@ -38,6 +81,7 @@ function toCodexRun(row, events = []) {
38
81
  taskSlug: row.task_slug,
39
82
  sessionId: row.session_id,
40
83
  prompt: row.prompt || '',
84
+ promptBlocks: parsePromptBlocks(row.prompt_blocks_json),
41
85
  status: row.status || 'running',
42
86
  responseMessage: row.response_message || '',
43
87
  errorMessage: row.error_message || '',
@@ -100,7 +144,7 @@ function getRunRowById(runId) {
100
144
  }
101
145
 
102
146
  return get(
103
- `SELECT id, task_slug, session_id, prompt, status, response_message, error_message, created_at, updated_at, started_at, finished_at
147
+ `SELECT id, task_slug, session_id, prompt, prompt_blocks_json, status, response_message, error_message, created_at, updated_at, started_at, finished_at
104
148
  FROM codex_runs
105
149
  WHERE id = ?`,
106
150
  [targetId]
@@ -208,6 +252,7 @@ export function listTaskCodexRunsWithOptions(taskSlug, options = {}) {
208
252
  runs.task_slug,
209
253
  runs.session_id,
210
254
  runs.prompt,
255
+ runs.prompt_blocks_json,
211
256
  runs.status,
212
257
  runs.response_message,
213
258
  runs.error_message,
@@ -227,6 +272,7 @@ export function listTaskCodexRunsWithOptions(taskSlug, options = {}) {
227
272
  runs.task_slug,
228
273
  runs.session_id,
229
274
  runs.prompt,
275
+ runs.prompt_blocks_json,
230
276
  runs.status,
231
277
  runs.response_message,
232
278
  runs.error_message,
@@ -271,6 +317,7 @@ export function createCodexRun(input = {}) {
271
317
  const taskSlug = String(input.taskSlug || '').trim()
272
318
  const sessionId = String(input.sessionId || '').trim()
273
319
  const prompt = String(input.prompt || '').trim()
320
+ const promptBlocks = normalizePromptBlocks(input.promptBlocks)
274
321
 
275
322
  if (!taskSlug) {
276
323
  throw new Error('缺少任务。')
@@ -298,11 +345,11 @@ export function createCodexRun(input = {}) {
298
345
  transaction(() => {
299
346
  run(
300
347
  `INSERT INTO codex_runs (
301
- id, task_slug, session_id, prompt, status,
348
+ id, task_slug, session_id, prompt, prompt_blocks_json, status,
302
349
  response_message, error_message, created_at, updated_at, started_at, finished_at
303
350
  )
304
- VALUES (?, ?, ?, ?, 'running', '', '', ?, ?, ?, NULL)`,
305
- [runId, task.slug, session.id, prompt, now, now, now]
351
+ VALUES (?, ?, ?, ?, ?, 'running', '', '', ?, ?, ?, NULL)`,
352
+ [runId, task.slug, session.id, prompt, JSON.stringify(promptBlocks), now, now, now]
306
353
  )
307
354
  })
308
355
 
@@ -206,6 +206,7 @@ function migrateToV1() {
206
206
  task_slug TEXT NOT NULL,
207
207
  session_id TEXT NOT NULL,
208
208
  prompt TEXT NOT NULL DEFAULT '',
209
+ prompt_blocks_json TEXT NOT NULL DEFAULT '[]',
209
210
  status TEXT NOT NULL,
210
211
  response_message TEXT NOT NULL DEFAULT '',
211
212
  error_message TEXT NOT NULL DEFAULT '',
@@ -295,11 +296,14 @@ function migrateToV1() {
295
296
  CREATE UNIQUE INDEX IF NOT EXISTS idx_run_git_final_snapshot_entries_scope_path
296
297
  ON run_git_final_snapshot_entries(run_id, path);
297
298
  `)
299
+ }
298
300
 
301
+ function applyAdditiveSchemaPatches() {
299
302
  const alterStatements = [
300
303
  `ALTER TABLE tasks ADD COLUMN auto_title TEXT NOT NULL DEFAULT ''`,
301
304
  `ALTER TABLE tasks ADD COLUMN last_prompt_preview TEXT NOT NULL DEFAULT ''`,
302
305
  `ALTER TABLE tasks ADD COLUMN codex_session_id TEXT NOT NULL DEFAULT ''`,
306
+ `ALTER TABLE codex_runs ADD COLUMN prompt_blocks_json TEXT NOT NULL DEFAULT '[]'`,
303
307
  `ALTER TABLE task_git_baselines ADD COLUMN branch_label TEXT NOT NULL DEFAULT ''`,
304
308
  `ALTER TABLE run_git_baselines ADD COLUMN branch_label TEXT NOT NULL DEFAULT ''`,
305
309
  `ALTER TABLE codex_run_events ADD COLUMN event_type TEXT NOT NULL DEFAULT 'event'`,
@@ -329,6 +333,8 @@ function ensureSchema() {
329
333
  writeSchemaVersion(1)
330
334
  }
331
335
 
336
+ applyAdditiveSchemaPatches()
337
+
332
338
  if (readSchemaVersion() < SCHEMA_VERSION) {
333
339
  writeSchemaVersion(SCHEMA_VERSION)
334
340
  }
@@ -57,6 +57,8 @@ import {
57
57
  searchWorkspaceEntries,
58
58
  } from './workspaceFiles.js'
59
59
  import { ensurePromptxStorageReady, serverRootDir } from './appPaths.js'
60
+ import { createRelayClient } from './relayClient.js'
61
+ import { getRelayConfigForClient, isRelayConfigManagedByEnv, writeStoredRelayConfig } from './relayConfig.js'
60
62
  import { createSseHub } from './sseHub.js'
61
63
 
62
64
  const app = Fastify({ logger: true })
@@ -80,6 +82,13 @@ function readPromptxVersion() {
80
82
  }
81
83
 
82
84
  const promptxVersion = readPromptxVersion()
85
+ const relayConfig = getRelayConfigForClient()
86
+ const relayClient = createRelayClient({
87
+ logger: app.log,
88
+ appVersion: promptxVersion,
89
+ localBaseUrl: process.env.PROMPTX_RELAY_LOCAL_BASE_URL || `http://127.0.0.1:${port}`,
90
+ ...relayConfig,
91
+ })
83
92
 
84
93
  let lastExpiredPurgeAt = 0
85
94
  const sseHub = createSseHub()
@@ -338,6 +347,32 @@ app.get('/api/meta', async () => ({
338
347
  visibilityOptions: VISIBILITY_OPTIONS,
339
348
  }))
340
349
 
350
+ app.get('/api/relay/status', async () => ({
351
+ relay: relayClient.getStatus(),
352
+ }))
353
+
354
+ app.get('/api/relay/config', async () => ({
355
+ config: {
356
+ ...getRelayConfigForClient(),
357
+ },
358
+ managedByEnv: isRelayConfigManagedByEnv(),
359
+ relay: relayClient.getStatus(),
360
+ }))
361
+
362
+ app.put('/api/relay/config', async (request) => {
363
+ const savedConfig = writeStoredRelayConfig(request.body || {})
364
+ relayClient.updateConfig({
365
+ ...savedConfig,
366
+ localBaseUrl: process.env.PROMPTX_RELAY_LOCAL_BASE_URL || `http://127.0.0.1:${port}`,
367
+ })
368
+
369
+ return {
370
+ config: getRelayConfigForClient(),
371
+ managedByEnv: isRelayConfigManagedByEnv(),
372
+ relay: relayClient.getStatus(),
373
+ }
374
+ })
375
+
341
376
  app.get('/api/events/stream', async (request, reply) => {
342
377
  reply.hijack()
343
378
  const requestOrigin = request.headers.origin
@@ -522,8 +557,9 @@ app.post('/api/tasks/:slug/codex-runs', async (request, reply) => {
522
557
  return reply.code(404).send({ message: '任务不存在。' })
523
558
  }
524
559
 
525
- const sessionId = String(request.body?.sessionId || '').trim()
526
- const prompt = String(request.body?.prompt || '').trim()
560
+ const sessionId = String(request.body?.sessionId || '').trim()
561
+ const prompt = String(request.body?.prompt || '').trim()
562
+ const promptBlocks = Array.isArray(request.body?.promptBlocks) ? request.body.promptBlocks : []
527
563
 
528
564
  if (!sessionId) {
529
565
  return reply.code(400).send({ message: '请先选择一个 PromptX 项目。' })
@@ -542,11 +578,12 @@ app.post('/api/tasks/:slug/codex-runs', async (request, reply) => {
542
578
  return reply.code(409).send({ message: '当前项目正在执行中,请等待完成后再发送。' })
543
579
  }
544
580
 
545
- const runRecord = createCodexRun({
546
- taskSlug: request.params.slug,
547
- sessionId,
548
- prompt,
549
- })
581
+ const runRecord = createCodexRun({
582
+ taskSlug: request.params.slug,
583
+ sessionId,
584
+ prompt,
585
+ promptBlocks,
586
+ })
550
587
 
551
588
  updateTaskCodexSession(request.params.slug, sessionId)
552
589
  codexRunRuntime.start(runRecord)
@@ -913,10 +950,11 @@ app.setErrorHandler((error, request, reply) => {
913
950
  markInterruptedCodexRuns()
914
951
  purgeExpiredContent(true)
915
952
 
916
- app.listen({ port, host }).then(() => {
917
- app.log.info(`server running at http://${host}:${port}`)
918
- buildServerAccessUrls(host, port).forEach((message) => {
919
- app.log.info(message)
920
- })
921
- })
953
+ app.listen({ port, host }).then(() => {
954
+ app.log.info(`server running at http://${host}:${port}`)
955
+ buildServerAccessUrls(host, port).forEach((message) => {
956
+ app.log.info(message)
957
+ })
958
+ relayClient.start()
959
+ })
922
960