replicas-engine 0.1.13 → 0.1.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 (2) hide show
  1. package/dist/src/index.js +639 -53
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -57,6 +57,438 @@ async function readJSONL(filePath) {
57
57
  import { readdir, stat } from "fs/promises";
58
58
  import { join } from "path";
59
59
  import { homedir } from "os";
60
+
61
+ // src/services/monolith-service.ts
62
+ var MonolithService = class {
63
+ async sendEvent(event) {
64
+ const monolithUrl = process.env.MONOLITH_URL;
65
+ const workspaceId = process.env.WORKSPACE_ID;
66
+ const engineSecret = process.env.REPLICAS_ENGINE_SECRET;
67
+ if (!monolithUrl || !workspaceId || !engineSecret) {
68
+ return;
69
+ }
70
+ try {
71
+ const response = await fetch(`${monolithUrl}/v1/engine/webhook`, {
72
+ method: "POST",
73
+ headers: {
74
+ Authorization: `Bearer ${engineSecret}`,
75
+ "X-Workspace-Id": workspaceId,
76
+ "Content-Type": "application/json"
77
+ },
78
+ body: JSON.stringify(event)
79
+ });
80
+ if (!response.ok) {
81
+ const errorText = await response.text();
82
+ console.error(`[MonolithService] Failed to send event: ${response.status} ${errorText}`);
83
+ }
84
+ } catch (error) {
85
+ console.error("[MonolithService] Failed to send event:", error);
86
+ }
87
+ }
88
+ };
89
+ var monolithService = new MonolithService();
90
+
91
+ // src/services/linear-event-converter.ts
92
+ function summarizeInput(input) {
93
+ if (!input) return "";
94
+ if (typeof input === "string") return input;
95
+ if (typeof input === "object") {
96
+ const obj = input;
97
+ if (obj.file_path) return String(obj.file_path);
98
+ if (obj.command) return String(obj.command);
99
+ if (obj.pattern) return `pattern: ${String(obj.pattern)}`;
100
+ if (obj.query) return String(obj.query);
101
+ if (obj.url) return String(obj.url);
102
+ const keys = Object.keys(obj);
103
+ if (keys.length > 0) {
104
+ const firstKey = keys[0];
105
+ return `${firstKey}: ${String(obj[firstKey])}`;
106
+ }
107
+ }
108
+ return "";
109
+ }
110
+ function convertClaudeEvent(event, linearSessionId) {
111
+ if (event.type === "assistant") {
112
+ const message = event;
113
+ const contentBlocks = message.message?.content || [];
114
+ for (const block of contentBlocks) {
115
+ if (block.type === "text" && block.text) {
116
+ return {
117
+ linearSessionId,
118
+ content: {
119
+ type: "thought",
120
+ body: block.text
121
+ }
122
+ };
123
+ }
124
+ if (block.type === "thinking" && block.text) {
125
+ return {
126
+ linearSessionId,
127
+ content: {
128
+ type: "thought",
129
+ body: block.text
130
+ }
131
+ };
132
+ }
133
+ if (block.type === "tool_use" && block.name) {
134
+ const toolName = block.name;
135
+ const parameter = summarizeInput(block.input);
136
+ let action = toolName;
137
+ switch (toolName) {
138
+ case "Bash":
139
+ action = "Running command";
140
+ break;
141
+ case "Edit":
142
+ action = "Editing file";
143
+ break;
144
+ case "Write":
145
+ action = "Writing file";
146
+ break;
147
+ case "Read":
148
+ action = "Reading file";
149
+ break;
150
+ case "Glob":
151
+ action = "Searching files";
152
+ break;
153
+ case "Grep":
154
+ action = "Searching code";
155
+ break;
156
+ case "WebSearch":
157
+ action = "Web search";
158
+ break;
159
+ case "WebFetch":
160
+ action = "Fetching URL";
161
+ break;
162
+ case "Task":
163
+ action = "Spawning subagent";
164
+ break;
165
+ case "TodoWrite":
166
+ action = "Updating plan";
167
+ break;
168
+ }
169
+ return {
170
+ linearSessionId,
171
+ content: {
172
+ type: "action",
173
+ action,
174
+ parameter
175
+ }
176
+ };
177
+ }
178
+ }
179
+ }
180
+ if (event.type === "user") {
181
+ const message = event;
182
+ const contentBlocks = message.message?.content || [];
183
+ for (const block of contentBlocks) {
184
+ if (block.type === "tool_result") {
185
+ let resultText = "";
186
+ if (typeof block.content === "string") {
187
+ resultText = block.content;
188
+ } else if (Array.isArray(block.content)) {
189
+ resultText = block.content.filter((c) => c.type === "text").map((c) => c.text || "").join("\n");
190
+ }
191
+ const isError = block.is_error || false;
192
+ const result = isError ? `Error: ${resultText}` : resultText || "Done";
193
+ return {
194
+ linearSessionId,
195
+ content: {
196
+ type: "action",
197
+ action: "Tool completed",
198
+ parameter: "",
199
+ result
200
+ }
201
+ };
202
+ }
203
+ }
204
+ }
205
+ return null;
206
+ }
207
+ function convertCodexEvent(event, linearSessionId) {
208
+ if (event.type === "turn.started") {
209
+ return {
210
+ linearSessionId,
211
+ content: {
212
+ type: "thought",
213
+ body: "Processing..."
214
+ }
215
+ };
216
+ }
217
+ if (event.type === "item.started") {
218
+ const item = event.item;
219
+ if (!item) return null;
220
+ if (item.type === "agent_message") {
221
+ const content = "content" in item ? String(item.content || "") : "";
222
+ if (content) {
223
+ return {
224
+ linearSessionId,
225
+ content: {
226
+ type: "thought",
227
+ body: content
228
+ }
229
+ };
230
+ }
231
+ }
232
+ if (item.type === "reasoning") {
233
+ const content = "content" in item ? String(item.content || "") : "";
234
+ if (content) {
235
+ return {
236
+ linearSessionId,
237
+ content: {
238
+ type: "thought",
239
+ body: content
240
+ }
241
+ };
242
+ }
243
+ }
244
+ if (item.type === "command_execution") {
245
+ const command = "command" in item ? String(item.command || "") : "";
246
+ return {
247
+ linearSessionId,
248
+ content: {
249
+ type: "action",
250
+ action: "Running command",
251
+ parameter: command
252
+ }
253
+ };
254
+ }
255
+ if (item.type === "file_change") {
256
+ const filePath = "file_path" in item ? String(item.file_path || "") : "";
257
+ return {
258
+ linearSessionId,
259
+ content: {
260
+ type: "action",
261
+ action: "File change",
262
+ parameter: filePath
263
+ }
264
+ };
265
+ }
266
+ if (item.type === "mcp_tool_call") {
267
+ const toolName = "tool_name" in item ? String(item.tool_name || "") : "";
268
+ return {
269
+ linearSessionId,
270
+ content: {
271
+ type: "action",
272
+ action: toolName || "Tool call",
273
+ parameter: ""
274
+ }
275
+ };
276
+ }
277
+ if (item.type === "web_search") {
278
+ const query2 = "query" in item ? String(item.query || "") : "";
279
+ return {
280
+ linearSessionId,
281
+ content: {
282
+ type: "action",
283
+ action: "Web search",
284
+ parameter: query2
285
+ }
286
+ };
287
+ }
288
+ if (item.type === "todo_list") {
289
+ return {
290
+ linearSessionId,
291
+ content: {
292
+ type: "action",
293
+ action: "Updating plan",
294
+ parameter: ""
295
+ }
296
+ };
297
+ }
298
+ }
299
+ if (event.type === "item.completed") {
300
+ const item = event.item;
301
+ if (!item) return null;
302
+ const getResult = () => {
303
+ if ("output" in item && item.output) return String(item.output);
304
+ if ("result" in item && item.result) return String(item.result);
305
+ if ("exit_code" in item) return `Exit code: ${item.exit_code}`;
306
+ return "Done";
307
+ };
308
+ if (item.type === "command_execution") {
309
+ const command = "command" in item ? String(item.command || "") : "";
310
+ return {
311
+ linearSessionId,
312
+ content: {
313
+ type: "action",
314
+ action: "Running command",
315
+ parameter: command,
316
+ result: getResult()
317
+ }
318
+ };
319
+ }
320
+ if (item.type === "file_change") {
321
+ const filePath = "file_path" in item ? String(item.file_path || "") : "";
322
+ return {
323
+ linearSessionId,
324
+ content: {
325
+ type: "action",
326
+ action: "File change",
327
+ parameter: filePath,
328
+ result: getResult()
329
+ }
330
+ };
331
+ }
332
+ if (item.type === "mcp_tool_call") {
333
+ const toolName = "tool_name" in item ? String(item.tool_name || "") : "";
334
+ return {
335
+ linearSessionId,
336
+ content: {
337
+ type: "action",
338
+ action: toolName || "Tool call",
339
+ parameter: "",
340
+ result: getResult()
341
+ }
342
+ };
343
+ }
344
+ if (item.type === "web_search") {
345
+ const query2 = "query" in item ? String(item.query || "") : "";
346
+ return {
347
+ linearSessionId,
348
+ content: {
349
+ type: "action",
350
+ action: "Web search",
351
+ parameter: query2,
352
+ result: getResult()
353
+ }
354
+ };
355
+ }
356
+ if (item.type === "todo_list") {
357
+ return {
358
+ linearSessionId,
359
+ content: {
360
+ type: "action",
361
+ action: "Updating plan",
362
+ parameter: "",
363
+ result: getResult()
364
+ }
365
+ };
366
+ }
367
+ if (item.type === "agent_message") {
368
+ const content = "content" in item ? String(item.content || "") : "";
369
+ if (content) {
370
+ return {
371
+ linearSessionId,
372
+ content: {
373
+ type: "thought",
374
+ body: content
375
+ }
376
+ };
377
+ }
378
+ }
379
+ }
380
+ return null;
381
+ }
382
+
383
+ // src/utils/git.ts
384
+ import { execSync } from "child_process";
385
+ var cachedPrUrl = null;
386
+ function runGitCommand(command, cwd) {
387
+ return execSync(command, {
388
+ cwd,
389
+ encoding: "utf-8",
390
+ stdio: ["pipe", "pipe", "pipe"]
391
+ }).trim();
392
+ }
393
+ function branchExists(branchName, cwd) {
394
+ try {
395
+ runGitCommand(`git rev-parse --verify ${branchName}`, cwd);
396
+ return true;
397
+ } catch {
398
+ return false;
399
+ }
400
+ }
401
+ function getCurrentBranch(cwd) {
402
+ try {
403
+ return runGitCommand("git rev-parse --abbrev-ref HEAD", cwd);
404
+ } catch (error) {
405
+ console.error("Error getting current branch:", error);
406
+ return null;
407
+ }
408
+ }
409
+ function getGitDiff(cwd) {
410
+ try {
411
+ const defaultBranch = process.env.REPLICAS_DEFAULT_BRANCH || "main";
412
+ const baseBranch = `origin/${defaultBranch}`;
413
+ const shortstat = execSync(`git diff ${baseBranch}...HEAD --shortstat -M`, {
414
+ cwd,
415
+ encoding: "utf-8",
416
+ stdio: ["pipe", "pipe", "pipe"]
417
+ }).trim();
418
+ let added = 0;
419
+ let removed = 0;
420
+ const addedMatch = shortstat.match(/(\d+) insertion/);
421
+ const removedMatch = shortstat.match(/(\d+) deletion/);
422
+ if (addedMatch) {
423
+ added = parseInt(addedMatch[1], 10);
424
+ }
425
+ if (removedMatch) {
426
+ removed = parseInt(removedMatch[1], 10);
427
+ }
428
+ const fullDiff = execSync(`git diff ${baseBranch}...HEAD -M -C`, {
429
+ cwd,
430
+ encoding: "utf-8",
431
+ stdio: ["pipe", "pipe", "pipe"]
432
+ });
433
+ return {
434
+ added,
435
+ removed,
436
+ fullDiff
437
+ };
438
+ } catch (error) {
439
+ console.error("Error getting git diff:", error);
440
+ return null;
441
+ }
442
+ }
443
+ function getPullRequestUrl(cwd) {
444
+ if (cachedPrUrl) {
445
+ return cachedPrUrl;
446
+ }
447
+ try {
448
+ const branch = getCurrentBranch(cwd);
449
+ if (!branch) {
450
+ return null;
451
+ }
452
+ try {
453
+ const remoteRef = execSync(`git ls-remote --heads origin ${branch}`, {
454
+ cwd,
455
+ encoding: "utf-8",
456
+ stdio: ["pipe", "pipe", "pipe"]
457
+ }).trim();
458
+ if (!remoteRef) {
459
+ return null;
460
+ }
461
+ } catch {
462
+ return null;
463
+ }
464
+ try {
465
+ const prInfo = execSync("gh pr view --json url --jq .url", {
466
+ cwd,
467
+ encoding: "utf-8",
468
+ stdio: ["pipe", "pipe", "pipe"]
469
+ }).trim();
470
+ if (prInfo) {
471
+ cachedPrUrl = prInfo;
472
+ return cachedPrUrl;
473
+ }
474
+ } catch {
475
+ return null;
476
+ }
477
+ return null;
478
+ } catch (error) {
479
+ console.error("Error checking for pull request:", error);
480
+ return null;
481
+ }
482
+ }
483
+ function getGitStatus(workingDirectory) {
484
+ return {
485
+ branch: getCurrentBranch(workingDirectory),
486
+ gitDiff: getGitDiff(workingDirectory),
487
+ prUrl: getPullRequestUrl(workingDirectory)
488
+ };
489
+ }
490
+
491
+ // src/services/codex-manager.ts
60
492
  var CodexManager = class {
61
493
  codex;
62
494
  currentThreadId = null;
@@ -81,6 +513,7 @@ var CodexManager = class {
81
513
  return this.processing;
82
514
  }
83
515
  async sendMessage(message, model, customInstructions) {
516
+ const linearSessionId = process.env.LINEAR_SESSION_ID;
84
517
  try {
85
518
  this.processing = true;
86
519
  if (!this.currentThread) {
@@ -89,32 +522,47 @@ var CodexManager = class {
89
522
  workingDirectory: this.workingDirectory,
90
523
  skipGitRepoCheck: true,
91
524
  sandboxMode: "danger-full-access",
92
- model: model || "gpt-5-codex"
525
+ model: model || "gpt-5.1-codex"
93
526
  });
94
527
  } else {
95
528
  this.currentThread = this.codex.startThread({
96
529
  workingDirectory: this.workingDirectory,
97
530
  skipGitRepoCheck: true,
98
531
  sandboxMode: "danger-full-access",
99
- model: model || "gpt-5-codex"
532
+ model: model || "gpt-5.1-codex"
100
533
  });
101
534
  if (customInstructions) {
102
535
  message = customInstructions + "\n" + message;
103
536
  }
537
+ const { events: events2 } = await this.currentThread.runStreamed("Hello");
538
+ for await (const event of events2) {
539
+ if (event.type === "thread.started") {
540
+ this.currentThreadId = event.thread_id;
541
+ break;
542
+ }
543
+ }
544
+ if (!this.currentThreadId && this.currentThread.id) {
545
+ this.currentThreadId = this.currentThread.id;
546
+ }
104
547
  }
105
548
  }
106
549
  const { events } = await this.currentThread.runStreamed(message);
107
550
  for await (const event of events) {
108
- if (event.type === "thread.started") {
109
- this.currentThreadId = event.thread_id;
110
- break;
551
+ if (linearSessionId) {
552
+ const linearEvent = convertCodexEvent(event, linearSessionId);
553
+ if (linearEvent) {
554
+ monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
555
+ });
556
+ }
111
557
  }
112
558
  }
113
- if (!this.currentThreadId && this.currentThread.id) {
114
- this.currentThreadId = this.currentThread.id;
115
- }
116
559
  } finally {
117
560
  this.processing = false;
561
+ if (linearSessionId) {
562
+ const status = getGitStatus(this.workingDirectory);
563
+ monolithService.sendEvent({ type: "agent_turn_complete", payload: { linearSessionId, status } }).catch(() => {
564
+ });
565
+ }
118
566
  }
119
567
  }
120
568
  async getHistory() {
@@ -371,6 +819,7 @@ var ClaudeManager = class {
371
819
  return this.processing;
372
820
  }
373
821
  async sendMessage(message, model, customInstructions) {
822
+ const linearSessionId = process.env.LINEAR_SESSION_ID;
374
823
  if (!message || !message.trim()) {
375
824
  throw new Error("Message cannot be empty");
376
825
  }
@@ -409,14 +858,26 @@ var ClaudeManager = class {
409
858
  append: customInstructions
410
859
  },
411
860
  env: process.env,
412
- model: model || "sonnet"
861
+ model: model || "opus"
413
862
  }
414
863
  });
415
864
  for await (const msg of response) {
416
865
  await this.handleMessage(msg);
866
+ if (linearSessionId) {
867
+ const linearEvent = convertClaudeEvent(msg, linearSessionId);
868
+ if (linearEvent) {
869
+ monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
870
+ });
871
+ }
872
+ }
417
873
  }
418
874
  } finally {
419
875
  this.processing = false;
876
+ if (linearSessionId) {
877
+ const status = getGitStatus(this.workingDirectory);
878
+ monolithService.sendEvent({ type: "agent_turn_complete", payload: { linearSessionId, status } }).catch(() => {
879
+ });
880
+ }
420
881
  }
421
882
  }
422
883
  async getHistory() {
@@ -585,53 +1046,170 @@ claude.post("/reset", async (c) => {
585
1046
  });
586
1047
  var claude_default = claude;
587
1048
 
588
- // src/utils/git.ts
589
- import { execSync } from "child_process";
590
- function getCurrentBranch(cwd) {
591
- try {
592
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
593
- cwd,
594
- encoding: "utf-8",
595
- stdio: ["pipe", "pipe", "pipe"]
596
- }).trim();
597
- return branch;
598
- } catch (error) {
599
- console.error("Error getting current branch:", error);
600
- return null;
1049
+ // src/services/github-token-manager.ts
1050
+ import { promises as fs } from "fs";
1051
+ import path from "path";
1052
+ var GitHubTokenManager = class {
1053
+ refreshInterval = null;
1054
+ REFRESH_INTERVAL_MS = 45 * 60 * 1e3;
1055
+ // 45 minutes
1056
+ async start() {
1057
+ const monolithUrl = process.env.MONOLITH_URL;
1058
+ const workspaceId = process.env.WORKSPACE_ID;
1059
+ const engineSecret = process.env.REPLICAS_ENGINE_SECRET;
1060
+ if (!monolithUrl || !workspaceId || !engineSecret) {
1061
+ console.log("[GitHubTokenManager] Skipping: missing MONOLITH_URL, WORKSPACE_ID, or REPLICAS_ENGINE_SECRET");
1062
+ return;
1063
+ }
1064
+ console.log("[GitHubTokenManager] Starting token refresh service");
1065
+ await this.refreshToken();
1066
+ this.refreshInterval = setInterval(() => {
1067
+ this.refreshToken().catch((error) => {
1068
+ console.error("[GitHubTokenManager] Scheduled refresh failed:", error);
1069
+ });
1070
+ }, this.REFRESH_INTERVAL_MS);
1071
+ console.log("[GitHubTokenManager] Token refresh scheduled every 45 minutes");
601
1072
  }
1073
+ stop() {
1074
+ if (this.refreshInterval) {
1075
+ clearInterval(this.refreshInterval);
1076
+ this.refreshInterval = null;
1077
+ console.log("[GitHubTokenManager] Stopped");
1078
+ }
1079
+ }
1080
+ async refreshToken() {
1081
+ const monolithUrl = process.env.MONOLITH_URL;
1082
+ const workspaceId = process.env.WORKSPACE_ID;
1083
+ const engineSecret = process.env.REPLICAS_ENGINE_SECRET;
1084
+ if (!monolithUrl || !workspaceId || !engineSecret) {
1085
+ console.log("[GitHubTokenManager] Refresh skipped: missing configuration");
1086
+ return;
1087
+ }
1088
+ console.log("[GitHubTokenManager] Refreshing GitHub token...");
1089
+ try {
1090
+ const response = await fetch(`${monolithUrl}/v1/engine/github/refresh-token`, {
1091
+ method: "POST",
1092
+ headers: {
1093
+ "Authorization": `Bearer ${engineSecret}`,
1094
+ "X-Workspace-Id": workspaceId,
1095
+ "Content-Type": "application/json"
1096
+ }
1097
+ });
1098
+ if (!response.ok) {
1099
+ const errorText = await response.text();
1100
+ throw new Error(`Token refresh failed: ${response.status} ${errorText}`);
1101
+ }
1102
+ const data = await response.json();
1103
+ await this.updateGitCredentials(data.token);
1104
+ process.env.GH_TOKEN = data.token;
1105
+ console.log(`[GitHubTokenManager] Token refreshed successfully, expires at ${data.expiresAt}`);
1106
+ } catch (error) {
1107
+ console.error("[GitHubTokenManager] Failed to refresh token:", error);
1108
+ }
1109
+ }
1110
+ async updateGitCredentials(token) {
1111
+ const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME;
1112
+ if (!workspaceHome) {
1113
+ console.warn("[GitHubTokenManager] No WORKSPACE_HOME or HOME set, skipping git credentials update");
1114
+ return;
1115
+ }
1116
+ const credentialsPath = path.join(workspaceHome, ".git-credentials");
1117
+ const credentialsContent = `https://x-access-token:${token}@github.com
1118
+ `;
1119
+ try {
1120
+ await fs.writeFile(credentialsPath, credentialsContent, { mode: 384 });
1121
+ console.log(`[GitHubTokenManager] Updated ${credentialsPath}`);
1122
+ } catch (error) {
1123
+ console.error("[GitHubTokenManager] Failed to update git credentials:", error);
1124
+ }
1125
+ }
1126
+ };
1127
+ var githubTokenManager = new GitHubTokenManager();
1128
+
1129
+ // src/services/git-init.ts
1130
+ import { existsSync } from "fs";
1131
+ import path2 from "path";
1132
+ var initializedBranch = null;
1133
+ function findAvailableBranchName(baseName, cwd) {
1134
+ if (!branchExists(baseName, cwd)) {
1135
+ return baseName;
1136
+ }
1137
+ return `${baseName}-${Date.now()}`;
602
1138
  }
603
- function getGitDiff(cwd) {
1139
+ async function initializeGitRepository() {
1140
+ const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME;
1141
+ const repoName = process.env.REPLICAS_REPO_NAME;
1142
+ const workspaceName = process.env.WORKSPACE_NAME;
1143
+ const defaultBranch = process.env.REPLICAS_DEFAULT_BRANCH || "main";
1144
+ if (!workspaceHome) {
1145
+ return {
1146
+ success: false,
1147
+ branch: null,
1148
+ error: "No WORKSPACE_HOME or HOME environment variable set"
1149
+ };
1150
+ }
1151
+ if (!repoName) {
1152
+ console.log("[GitInit] No REPLICAS_REPO_NAME set, skipping git initialization");
1153
+ return {
1154
+ success: true,
1155
+ branch: null
1156
+ };
1157
+ }
1158
+ if (!workspaceName) {
1159
+ return {
1160
+ success: false,
1161
+ branch: null,
1162
+ error: "No WORKSPACE_NAME environment variable set"
1163
+ };
1164
+ }
1165
+ const repoPath = path2.join(workspaceHome, "workspaces", repoName);
1166
+ if (!existsSync(repoPath)) {
1167
+ console.log(`[GitInit] Repository directory does not exist: ${repoPath}`);
1168
+ console.log("[GitInit] Waiting for initializer to clone the repository...");
1169
+ return {
1170
+ success: true,
1171
+ branch: null
1172
+ };
1173
+ }
1174
+ if (!existsSync(path2.join(repoPath, ".git"))) {
1175
+ return {
1176
+ success: false,
1177
+ branch: null,
1178
+ error: `Directory exists but is not a git repository: ${repoPath}`
1179
+ };
1180
+ }
1181
+ console.log(`[GitInit] Initializing repository at ${repoPath}`);
604
1182
  try {
605
- const defaultBranch = process.env.REPLICAS_DEFAULT_BRANCH || "main";
606
- const baseBranch = `origin/${defaultBranch}`;
607
- const shortstat = execSync(`git diff ${baseBranch}...HEAD --shortstat -M`, {
608
- cwd,
609
- encoding: "utf-8",
610
- stdio: ["pipe", "pipe", "pipe"]
611
- }).trim();
612
- let added = 0;
613
- let removed = 0;
614
- const addedMatch = shortstat.match(/(\d+) insertion/);
615
- const removedMatch = shortstat.match(/(\d+) deletion/);
616
- if (addedMatch) {
617
- added = parseInt(addedMatch[1], 10);
1183
+ console.log("[GitInit] Fetching all remotes...");
1184
+ runGitCommand("git fetch --all --prune", repoPath);
1185
+ console.log(`[GitInit] Checking out default branch: ${defaultBranch}`);
1186
+ runGitCommand(`git checkout ${defaultBranch}`, repoPath);
1187
+ console.log("[GitInit] Pulling latest changes...");
1188
+ try {
1189
+ runGitCommand("git pull --rebase --autostash", repoPath);
1190
+ } catch (pullError) {
1191
+ console.warn("[GitInit] Pull had issues, continuing anyway:", pullError);
618
1192
  }
619
- if (removedMatch) {
620
- removed = parseInt(removedMatch[1], 10);
1193
+ const branchName = findAvailableBranchName(workspaceName, repoPath);
1194
+ if (branchName !== workspaceName) {
1195
+ console.log(`[GitInit] Branch "${workspaceName}" already exists, using "${branchName}" instead`);
621
1196
  }
622
- const fullDiff = execSync(`git diff ${baseBranch}...HEAD -M -C`, {
623
- cwd,
624
- encoding: "utf-8",
625
- stdio: ["pipe", "pipe", "pipe"]
626
- });
1197
+ console.log(`[GitInit] Creating workspace branch: ${branchName}`);
1198
+ runGitCommand(`git checkout -b ${branchName}`, repoPath);
1199
+ initializedBranch = branchName;
1200
+ console.log(`[GitInit] Successfully initialized on branch: ${branchName}`);
627
1201
  return {
628
- added,
629
- removed,
630
- fullDiff
1202
+ success: true,
1203
+ branch: branchName
631
1204
  };
632
1205
  } catch (error) {
633
- console.error("Error getting git diff:", error);
634
- return null;
1206
+ const errorMessage = error instanceof Error ? error.message : String(error);
1207
+ console.error("[GitInit] Failed to initialize repository:", errorMessage);
1208
+ return {
1209
+ success: false,
1210
+ branch: null,
1211
+ error: errorMessage
1212
+ };
635
1213
  }
636
1214
  }
637
1215
 
@@ -665,15 +1243,14 @@ app.get("/status", async (c) => {
665
1243
  const isClaudeUsed = claudeHistory.thread_id !== null;
666
1244
  const claudeStatus = await claudeManager.getStatus();
667
1245
  const workingDirectory = claudeStatus.working_directory;
668
- const branch = getCurrentBranch(workingDirectory);
669
- const gitDiff = getGitDiff(workingDirectory);
670
1246
  return c.json({
671
1247
  isCodexProcessing,
672
1248
  isClaudeProcessing,
673
1249
  isCodexUsed,
674
1250
  isClaudeUsed,
675
- branch,
676
- gitDiff
1251
+ ...getGitStatus(workingDirectory),
1252
+ linearBetaEnabled: true
1253
+ // TODO: delete
677
1254
  });
678
1255
  } catch (error) {
679
1256
  console.error("Error getting workspace status:", error);
@@ -695,7 +1272,16 @@ serve(
695
1272
  fetch: app.fetch,
696
1273
  port
697
1274
  },
698
- (info) => {
1275
+ async (info) => {
699
1276
  console.log(`Replicas Engine running on port ${info.port}`);
1277
+ const gitResult = await initializeGitRepository();
1278
+ if (gitResult.success) {
1279
+ if (gitResult.branch) {
1280
+ console.log(`Git initialized on branch: ${gitResult.branch}`);
1281
+ }
1282
+ } else {
1283
+ console.warn(`Git initialization warning: ${gitResult.error}`);
1284
+ }
1285
+ await githubTokenManager.start();
700
1286
  }
701
1287
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",