replicas-engine 0.1.14 → 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 +635 -50
  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,20 +522,20 @@ 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
  }
104
- const { events } = await this.currentThread.runStreamed("Hello");
105
- for await (const event of events) {
537
+ const { events: events2 } = await this.currentThread.runStreamed("Hello");
538
+ for await (const event of events2) {
106
539
  if (event.type === "thread.started") {
107
540
  this.currentThreadId = event.thread_id;
108
541
  break;
@@ -113,9 +546,23 @@ var CodexManager = class {
113
546
  }
114
547
  }
115
548
  }
116
- await this.currentThread.run(message);
549
+ const { events } = await this.currentThread.runStreamed(message);
550
+ for await (const event of events) {
551
+ if (linearSessionId) {
552
+ const linearEvent = convertCodexEvent(event, linearSessionId);
553
+ if (linearEvent) {
554
+ monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
555
+ });
556
+ }
557
+ }
558
+ }
117
559
  } finally {
118
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
+ }
119
566
  }
120
567
  }
121
568
  async getHistory() {
@@ -372,6 +819,7 @@ var ClaudeManager = class {
372
819
  return this.processing;
373
820
  }
374
821
  async sendMessage(message, model, customInstructions) {
822
+ const linearSessionId = process.env.LINEAR_SESSION_ID;
375
823
  if (!message || !message.trim()) {
376
824
  throw new Error("Message cannot be empty");
377
825
  }
@@ -410,14 +858,26 @@ var ClaudeManager = class {
410
858
  append: customInstructions
411
859
  },
412
860
  env: process.env,
413
- model: model || "sonnet"
861
+ model: model || "opus"
414
862
  }
415
863
  });
416
864
  for await (const msg of response) {
417
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
+ }
418
873
  }
419
874
  } finally {
420
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
+ }
421
881
  }
422
882
  }
423
883
  async getHistory() {
@@ -586,53 +1046,170 @@ claude.post("/reset", async (c) => {
586
1046
  });
587
1047
  var claude_default = claude;
588
1048
 
589
- // src/utils/git.ts
590
- import { execSync } from "child_process";
591
- function getCurrentBranch(cwd) {
592
- try {
593
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
594
- cwd,
595
- encoding: "utf-8",
596
- stdio: ["pipe", "pipe", "pipe"]
597
- }).trim();
598
- return branch;
599
- } catch (error) {
600
- console.error("Error getting current branch:", error);
601
- 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");
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
+ }
602
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()}`;
603
1138
  }
604
- 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}`);
605
1182
  try {
606
- const defaultBranch = process.env.REPLICAS_DEFAULT_BRANCH || "main";
607
- const baseBranch = `origin/${defaultBranch}`;
608
- const shortstat = execSync(`git diff ${baseBranch}...HEAD --shortstat -M`, {
609
- cwd,
610
- encoding: "utf-8",
611
- stdio: ["pipe", "pipe", "pipe"]
612
- }).trim();
613
- let added = 0;
614
- let removed = 0;
615
- const addedMatch = shortstat.match(/(\d+) insertion/);
616
- const removedMatch = shortstat.match(/(\d+) deletion/);
617
- if (addedMatch) {
618
- 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);
619
1192
  }
620
- if (removedMatch) {
621
- 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`);
622
1196
  }
623
- const fullDiff = execSync(`git diff ${baseBranch}...HEAD -M -C`, {
624
- cwd,
625
- encoding: "utf-8",
626
- stdio: ["pipe", "pipe", "pipe"]
627
- });
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}`);
628
1201
  return {
629
- added,
630
- removed,
631
- fullDiff
1202
+ success: true,
1203
+ branch: branchName
632
1204
  };
633
1205
  } catch (error) {
634
- console.error("Error getting git diff:", error);
635
- 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
+ };
636
1213
  }
637
1214
  }
638
1215
 
@@ -666,15 +1243,14 @@ app.get("/status", async (c) => {
666
1243
  const isClaudeUsed = claudeHistory.thread_id !== null;
667
1244
  const claudeStatus = await claudeManager.getStatus();
668
1245
  const workingDirectory = claudeStatus.working_directory;
669
- const branch = getCurrentBranch(workingDirectory);
670
- const gitDiff = getGitDiff(workingDirectory);
671
1246
  return c.json({
672
1247
  isCodexProcessing,
673
1248
  isClaudeProcessing,
674
1249
  isCodexUsed,
675
1250
  isClaudeUsed,
676
- branch,
677
- gitDiff
1251
+ ...getGitStatus(workingDirectory),
1252
+ linearBetaEnabled: true
1253
+ // TODO: delete
678
1254
  });
679
1255
  } catch (error) {
680
1256
  console.error("Error getting workspace status:", error);
@@ -696,7 +1272,16 @@ serve(
696
1272
  fetch: app.fetch,
697
1273
  port
698
1274
  },
699
- (info) => {
1275
+ async (info) => {
700
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();
701
1286
  }
702
1287
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.14",
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",