@muyichengshayu/promptx 0.1.0

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.
@@ -0,0 +1,212 @@
1
+ import { getPromptxCodexSessionById, updatePromptxCodexSession } from './codexSessions.js'
2
+ import { streamPromptToCodexSession } from './codex.js'
3
+ import { appendCodexRunEvent, updateCodexRun } from './codexRuns.js'
4
+
5
+ export function createCodexRunRuntime(options = {}) {
6
+ const {
7
+ decorateSession = (session) => session,
8
+ onRunEvent = () => {},
9
+ onRunUpdated = () => {},
10
+ onSessionChanged = () => {},
11
+ } = options
12
+
13
+ const activeControllers = new Map()
14
+
15
+ function getController(runId = '') {
16
+ return activeControllers.get(String(runId || '').trim()) || null
17
+ }
18
+
19
+ function setController(runId = '', controller) {
20
+ const normalizedRunId = String(runId || '').trim()
21
+ if (!normalizedRunId || !controller) {
22
+ return
23
+ }
24
+
25
+ activeControllers.set(normalizedRunId, controller)
26
+ }
27
+
28
+ function clearController(runId = '') {
29
+ const normalizedRunId = String(runId || '').trim()
30
+ if (!normalizedRunId) {
31
+ return
32
+ }
33
+
34
+ activeControllers.delete(normalizedRunId)
35
+ }
36
+
37
+ function notifyListeners(runId = '', payload = {}) {
38
+ const controller = getController(runId)
39
+ if (!controller?.listeners?.size) {
40
+ return
41
+ }
42
+
43
+ controller.listeners.forEach((listener) => {
44
+ try {
45
+ listener(payload)
46
+ } catch {
47
+ // Ignore observer failures to avoid affecting the run lifecycle.
48
+ }
49
+ })
50
+ }
51
+
52
+ function subscribe(runId = '', listener) {
53
+ const controller = getController(runId)
54
+ if (!controller || typeof listener !== 'function') {
55
+ return () => {}
56
+ }
57
+
58
+ controller.listeners.add(listener)
59
+ return () => {
60
+ controller.listeners.delete(listener)
61
+ }
62
+ }
63
+
64
+ function start(runRecord) {
65
+ const runId = String(runRecord?.id || '').trim()
66
+ if (!runId) {
67
+ return
68
+ }
69
+
70
+ const session = getPromptxCodexSessionById(runRecord.sessionId)
71
+ if (!session) {
72
+ updateCodexRun(runId, {
73
+ status: 'error',
74
+ errorMessage: '没有找到对应的 PromptX 项目。',
75
+ finishedAt: new Date().toISOString(),
76
+ })
77
+ return
78
+ }
79
+
80
+ let eventSeq = 0
81
+ let stopRequested = false
82
+
83
+ const persistRunEvent = (payload) => {
84
+ eventSeq += 1
85
+ const event = appendCodexRunEvent(runId, eventSeq, payload)
86
+ if (event) {
87
+ onRunEvent({
88
+ taskSlug: runRecord.taskSlug,
89
+ runId,
90
+ event,
91
+ })
92
+ notifyListeners(runId, {
93
+ type: 'event',
94
+ event,
95
+ })
96
+ }
97
+ return event
98
+ }
99
+
100
+ persistRunEvent({
101
+ type: 'session',
102
+ session: decorateSession(session),
103
+ })
104
+
105
+ const stream = streamPromptToCodexSession(session, runRecord.prompt, {
106
+ onEvent(payload) {
107
+ persistRunEvent(payload)
108
+ },
109
+ onThreadStarted(threadId) {
110
+ const updatedSession = updatePromptxCodexSession(session.id, {
111
+ codexThreadId: threadId,
112
+ })
113
+
114
+ if (updatedSession) {
115
+ persistRunEvent({
116
+ type: 'session.updated',
117
+ session: decorateSession(updatedSession),
118
+ })
119
+ onSessionChanged({
120
+ sessionId: session.id,
121
+ })
122
+ }
123
+ },
124
+ })
125
+
126
+ const controller = {
127
+ listeners: new Set(),
128
+ cancel() {
129
+ stopRequested = true
130
+ stream.cancel()
131
+ },
132
+ get stopRequested() {
133
+ return stopRequested
134
+ },
135
+ }
136
+ setController(runId, controller)
137
+
138
+ stream.result
139
+ .then((result) => {
140
+ const nextRun = updateCodexRun(runId, {
141
+ status: 'completed',
142
+ responseMessage: result.message || '',
143
+ errorMessage: '',
144
+ finishedAt: new Date().toISOString(),
145
+ })
146
+ notifyListeners(runId, {
147
+ type: 'run',
148
+ run: nextRun,
149
+ })
150
+ onRunUpdated({
151
+ taskSlug: runRecord.taskSlug,
152
+ runId,
153
+ })
154
+ onSessionChanged({
155
+ sessionId: session.id,
156
+ })
157
+ })
158
+ .catch((error) => {
159
+ if (stopRequested) {
160
+ persistRunEvent({
161
+ type: 'stopped',
162
+ message: '执行已手动停止。',
163
+ })
164
+ const nextRun = updateCodexRun(runId, {
165
+ status: 'stopped',
166
+ responseMessage: '',
167
+ errorMessage: '',
168
+ finishedAt: new Date().toISOString(),
169
+ })
170
+ notifyListeners(runId, {
171
+ type: 'run',
172
+ run: nextRun,
173
+ })
174
+ onRunUpdated({
175
+ taskSlug: runRecord.taskSlug,
176
+ runId,
177
+ })
178
+ onSessionChanged({
179
+ sessionId: session.id,
180
+ })
181
+ return
182
+ }
183
+
184
+ const nextRun = updateCodexRun(runId, {
185
+ status: 'error',
186
+ errorMessage: error.message || 'Codex 执行失败。',
187
+ finishedAt: new Date().toISOString(),
188
+ })
189
+ notifyListeners(runId, {
190
+ type: 'run',
191
+ run: nextRun,
192
+ })
193
+ onRunUpdated({
194
+ taskSlug: runRecord.taskSlug,
195
+ runId,
196
+ })
197
+ onSessionChanged({
198
+ sessionId: session.id,
199
+ })
200
+ })
201
+ .finally(() => {
202
+ notifyListeners(runId, { type: 'close' })
203
+ clearController(runId)
204
+ })
205
+ }
206
+
207
+ return {
208
+ getController,
209
+ subscribe,
210
+ start,
211
+ }
212
+ }