replicas-engine 0.1.6 → 0.1.8

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.
@@ -1,11 +1,10 @@
1
1
  import { Hono } from 'hono';
2
- import { stream } from 'hono/streaming';
3
2
  import { CodexManager } from '../services/codex-manager.js';
4
3
  const codex = new Hono();
5
4
  const codexManager = new CodexManager();
6
5
  /**
7
6
  * POST /codex/send
8
- * send a message to Codex and stream events back via Server-Sent Events (SSE)
7
+ * send a message to Codex (non-blocking, writes to JSONL automatically)
9
8
  */
10
9
  codex.post('/send', async (c) => {
11
10
  try {
@@ -14,27 +13,10 @@ codex.post('/send', async (c) => {
14
13
  if (!message || typeof message !== 'string') {
15
14
  return c.json({ error: 'Message is required and must be a string' }, 400);
16
15
  }
17
- return stream(c, async (stream) => {
18
- c.header('Content-Type', 'text/event-stream');
19
- c.header('Cache-Control', 'no-cache');
20
- c.header('Connection', 'keep-alive');
21
- stream.onAbort(() => {
22
- console.log('Client aborted SSE connection');
23
- });
24
- try {
25
- for await (const event of codexManager.sendMessage(message)) {
26
- const sseData = `event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
27
- await stream.write(sseData);
28
- }
29
- await stream.write('event: done\ndata: {}\n\n');
30
- }
31
- catch (error) {
32
- console.error('Error during Codex streaming:', error);
33
- const errorData = `event: error\ndata: ${JSON.stringify({
34
- message: error instanceof Error ? error.message : 'Unknown error occurred',
35
- })}\n\n`;
36
- await stream.write(errorData);
37
- }
16
+ await codexManager.sendMessage(message);
17
+ return c.json({
18
+ success: true,
19
+ message: 'Message sent successfully',
38
20
  });
39
21
  }
40
22
  catch (error) {
@@ -69,6 +51,27 @@ codex.get('/history', async (c) => {
69
51
  }, 500);
70
52
  }
71
53
  });
54
+ /**
55
+ * GET /codex/updates
56
+ * get new events since a given timestamp (for polling)
57
+ */
58
+ codex.get('/updates', async (c) => {
59
+ try {
60
+ const since = c.req.query('since') || '';
61
+ if (!since) {
62
+ return c.json({ error: 'Missing "since" query parameter' }, 400);
63
+ }
64
+ const updates = await codexManager.getUpdates(since);
65
+ return c.json(updates);
66
+ }
67
+ catch (error) {
68
+ console.error('Error in /codex/updates:', error);
69
+ return c.json({
70
+ error: 'Failed to retrieve updates',
71
+ details: error instanceof Error ? error.message : 'Unknown error',
72
+ }, 500);
73
+ }
74
+ });
72
75
  /**
73
76
  * GET /codex/status
74
77
  * get current thread status and information
@@ -1,4 +1,5 @@
1
1
  import { Codex, Thread } from '@openai/codex-sdk';
2
+ import { randomUUID } from 'crypto';
2
3
  import { findSessionFile, readJSONL } from '../utils/jsonl-reader.js';
3
4
  export class CodexManager {
4
5
  codex;
@@ -22,7 +23,7 @@ export class CodexManager {
22
23
  }
23
24
  }
24
25
  }
25
- async *sendMessage(message) {
26
+ async sendMessage(message) {
26
27
  if (!this.currentThread) {
27
28
  if (this.currentThreadId) {
28
29
  console.log(`Resuming thread ${this.currentThreadId}`);
@@ -39,16 +40,26 @@ export class CodexManager {
39
40
  skipGitRepoCheck: true,
40
41
  sandboxMode: 'danger-full-access',
41
42
  });
43
+ if (this.currentThread.id) {
44
+ this.currentThreadId = this.currentThread.id;
45
+ console.log(`Thread started: ${this.currentThreadId}`);
46
+ }
47
+ // prime the thread with system instructions if thread is new
48
+ const workspaceName = process.env.WORKSPACE_NAME || 'workspace';
49
+ const workspaceBranchSegment = workspaceName
50
+ .trim()
51
+ .toLowerCase()
52
+ .replace(/[^a-z0-9]+/g, '-')
53
+ .replace(/^-+|-+$/g, '') || 'workspace';
54
+ const uuid = randomUUID().slice(0, 8);
55
+ const branchName = `replicas/${workspaceBranchSegment}-${uuid}`;
56
+ const systemMessage = `<replicas_important_instructions>When completing solutions, push your changes to branch ${branchName} and to origin. Greet the user.</replicas_important_instructions>`;
57
+ console.log('Priming thread with system instructions');
58
+ await this.currentThread.run(systemMessage);
59
+ console.log('Thread primed successfully');
42
60
  }
43
61
  }
44
- const { events } = await this.currentThread.runStreamed(message);
45
- for await (const event of events) {
46
- if (event.type === 'thread.started') {
47
- this.currentThreadId = event.thread_id;
48
- console.log(`Thread started: ${this.currentThreadId}`);
49
- }
50
- yield event;
51
- }
62
+ await this.currentThread.run(message);
52
63
  }
53
64
  async getHistory() {
54
65
  if (!this.currentThreadId) {
@@ -67,9 +78,13 @@ export class CodexManager {
67
78
  }
68
79
  console.log(`Reading session file: ${sessionFile}`);
69
80
  const events = await readJSONL(sessionFile);
81
+ const filteredEvents = events.filter((event) => {
82
+ const eventStr = JSON.stringify(event);
83
+ return !eventStr.includes('<replicas_important_instructions>');
84
+ });
70
85
  return {
71
86
  thread_id: this.currentThreadId,
72
- events,
87
+ events: filteredEvents,
73
88
  };
74
89
  }
75
90
  async getStatus() {
@@ -92,4 +107,32 @@ export class CodexManager {
92
107
  getThreadId() {
93
108
  return this.currentThreadId;
94
109
  }
110
+ async getUpdates(since) {
111
+ if (!this.currentThreadId) {
112
+ return {
113
+ events: [],
114
+ isComplete: true,
115
+ };
116
+ }
117
+ const sessionFile = await findSessionFile(this.currentThreadId);
118
+ if (!sessionFile) {
119
+ return {
120
+ events: [],
121
+ isComplete: true,
122
+ };
123
+ }
124
+ const allEvents = await readJSONL(sessionFile);
125
+ // Filter events that occurred after the 'since' timestamp
126
+ const filteredEvents = allEvents.filter((event) => {
127
+ return event.timestamp > since;
128
+ });
129
+ // Check if thread is complete by looking for turn.completed or error events
130
+ const isComplete = this.currentThread === null ||
131
+ allEvents.some((event) => event.type === 'event_msg' &&
132
+ (event.payload?.type === 'turn.completed' || event.payload?.type === 'error'));
133
+ return {
134
+ events: filteredEvents,
135
+ isComplete,
136
+ };
137
+ }
95
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",