replicas-engine 0.1.16 → 0.1.18
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/dist/src/index.js +407 -52
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -384,7 +384,7 @@ function convertCodexEvent(event, linearSessionId) {
|
|
|
384
384
|
|
|
385
385
|
// src/utils/git.ts
|
|
386
386
|
import { execSync } from "child_process";
|
|
387
|
-
var
|
|
387
|
+
var cachedPr = null;
|
|
388
388
|
function runGitCommand(command, cwd) {
|
|
389
389
|
return execSync(command, {
|
|
390
390
|
cwd,
|
|
@@ -443,16 +443,17 @@ function getGitDiff(cwd) {
|
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
445
|
function getPullRequestUrl(cwd) {
|
|
446
|
-
if (cachedPrUrl) {
|
|
447
|
-
return cachedPrUrl;
|
|
448
|
-
}
|
|
449
446
|
try {
|
|
450
|
-
const
|
|
451
|
-
if (!
|
|
447
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
448
|
+
if (!currentBranch) {
|
|
452
449
|
return null;
|
|
453
450
|
}
|
|
451
|
+
if (cachedPr && cachedPr.branch === currentBranch) {
|
|
452
|
+
return cachedPr.prUrl;
|
|
453
|
+
}
|
|
454
|
+
cachedPr = null;
|
|
454
455
|
try {
|
|
455
|
-
const remoteRef = execSync(`git ls-remote --heads origin ${
|
|
456
|
+
const remoteRef = execSync(`git ls-remote --heads origin ${currentBranch}`, {
|
|
456
457
|
cwd,
|
|
457
458
|
encoding: "utf-8",
|
|
458
459
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -470,8 +471,11 @@ function getPullRequestUrl(cwd) {
|
|
|
470
471
|
stdio: ["pipe", "pipe", "pipe"]
|
|
471
472
|
}).trim();
|
|
472
473
|
if (prInfo) {
|
|
473
|
-
|
|
474
|
-
|
|
474
|
+
cachedPr = {
|
|
475
|
+
prUrl: prInfo,
|
|
476
|
+
branch: currentBranch
|
|
477
|
+
};
|
|
478
|
+
return cachedPr.prUrl;
|
|
475
479
|
}
|
|
476
480
|
} catch {
|
|
477
481
|
return null;
|
|
@@ -490,13 +494,120 @@ function getGitStatus(workingDirectory) {
|
|
|
490
494
|
};
|
|
491
495
|
}
|
|
492
496
|
|
|
497
|
+
// src/services/message-queue.ts
|
|
498
|
+
var MessageQueue = class {
|
|
499
|
+
queue = [];
|
|
500
|
+
processing = false;
|
|
501
|
+
currentMessageId = null;
|
|
502
|
+
messageIdCounter = 0;
|
|
503
|
+
processMessage;
|
|
504
|
+
constructor(processMessage) {
|
|
505
|
+
this.processMessage = processMessage;
|
|
506
|
+
}
|
|
507
|
+
generateMessageId() {
|
|
508
|
+
return `msg_${Date.now()}_${++this.messageIdCounter}`;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Add a message to the queue or start processing immediately if not busy
|
|
512
|
+
* @returns Object indicating whether the message was queued or started processing
|
|
513
|
+
*/
|
|
514
|
+
async enqueue(message, model, customInstructions) {
|
|
515
|
+
const messageId = this.generateMessageId();
|
|
516
|
+
const queuedMessage = {
|
|
517
|
+
id: messageId,
|
|
518
|
+
message,
|
|
519
|
+
model,
|
|
520
|
+
customInstructions,
|
|
521
|
+
queuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
522
|
+
};
|
|
523
|
+
if (this.processing) {
|
|
524
|
+
this.queue.push(queuedMessage);
|
|
525
|
+
return {
|
|
526
|
+
queued: true,
|
|
527
|
+
messageId,
|
|
528
|
+
position: this.queue.length
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
this.startProcessing(queuedMessage);
|
|
532
|
+
return {
|
|
533
|
+
queued: false,
|
|
534
|
+
messageId,
|
|
535
|
+
position: 0
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
async startProcessing(queuedMessage) {
|
|
539
|
+
this.processing = true;
|
|
540
|
+
this.currentMessageId = queuedMessage.id;
|
|
541
|
+
try {
|
|
542
|
+
await this.processMessage(
|
|
543
|
+
queuedMessage.message,
|
|
544
|
+
queuedMessage.model,
|
|
545
|
+
queuedMessage.customInstructions
|
|
546
|
+
);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
console.error("[MessageQueue] Error processing message:", error);
|
|
549
|
+
} finally {
|
|
550
|
+
this.processing = false;
|
|
551
|
+
this.currentMessageId = null;
|
|
552
|
+
await this.processNextInQueue();
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async processNextInQueue() {
|
|
556
|
+
const nextMessage = this.queue.shift();
|
|
557
|
+
if (nextMessage) {
|
|
558
|
+
await this.startProcessing(nextMessage);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Check if currently processing a message
|
|
563
|
+
*/
|
|
564
|
+
isProcessing() {
|
|
565
|
+
return this.processing;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Get the current queue length
|
|
569
|
+
*/
|
|
570
|
+
getQueueLength() {
|
|
571
|
+
return this.queue.length;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Get full queue status
|
|
575
|
+
*/
|
|
576
|
+
getStatus() {
|
|
577
|
+
return {
|
|
578
|
+
isProcessing: this.processing,
|
|
579
|
+
queueLength: this.queue.length,
|
|
580
|
+
currentMessageId: this.currentMessageId,
|
|
581
|
+
queuedMessages: this.queue.map((msg, index) => ({
|
|
582
|
+
id: msg.id,
|
|
583
|
+
queuedAt: msg.queuedAt,
|
|
584
|
+
position: index + 1
|
|
585
|
+
}))
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Clear the queue (does not stop current processing)
|
|
590
|
+
*/
|
|
591
|
+
clearQueue() {
|
|
592
|
+
this.queue = [];
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Reset everything including clearing processing state
|
|
596
|
+
*/
|
|
597
|
+
reset() {
|
|
598
|
+
this.queue = [];
|
|
599
|
+
this.processing = false;
|
|
600
|
+
this.currentMessageId = null;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
493
604
|
// src/services/codex-manager.ts
|
|
494
605
|
var CodexManager = class {
|
|
495
606
|
codex;
|
|
496
607
|
currentThreadId = null;
|
|
497
608
|
currentThread = null;
|
|
498
609
|
workingDirectory;
|
|
499
|
-
|
|
610
|
+
messageQueue;
|
|
500
611
|
constructor(workingDirectory) {
|
|
501
612
|
this.codex = new Codex();
|
|
502
613
|
if (workingDirectory) {
|
|
@@ -510,14 +621,38 @@ var CodexManager = class {
|
|
|
510
621
|
this.workingDirectory = workspaceHome;
|
|
511
622
|
}
|
|
512
623
|
}
|
|
624
|
+
this.messageQueue = new MessageQueue(this.processMessageInternal.bind(this));
|
|
513
625
|
}
|
|
514
626
|
isProcessing() {
|
|
515
|
-
return this.
|
|
627
|
+
return this.messageQueue.isProcessing();
|
|
516
628
|
}
|
|
629
|
+
/**
|
|
630
|
+
* Enqueue a message for processing. If not currently processing, starts immediately.
|
|
631
|
+
* If already processing, adds to queue.
|
|
632
|
+
* @returns Object with queued status, messageId, and position in queue
|
|
633
|
+
*/
|
|
634
|
+
async enqueueMessage(message, model, customInstructions) {
|
|
635
|
+
return this.messageQueue.enqueue(message, model, customInstructions);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Get the current queue status
|
|
639
|
+
*/
|
|
640
|
+
getQueueStatus() {
|
|
641
|
+
return this.messageQueue.getStatus();
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Legacy sendMessage method - now uses the queue internally
|
|
645
|
+
* @deprecated Use enqueueMessage for better control over queue status
|
|
646
|
+
*/
|
|
517
647
|
async sendMessage(message, model, customInstructions) {
|
|
648
|
+
await this.enqueueMessage(message, model, customInstructions);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Internal method that actually processes the message
|
|
652
|
+
*/
|
|
653
|
+
async processMessageInternal(message, model, customInstructions) {
|
|
518
654
|
const linearSessionId = process.env.LINEAR_SESSION_ID;
|
|
519
655
|
try {
|
|
520
|
-
this.processing = true;
|
|
521
656
|
if (!this.currentThread) {
|
|
522
657
|
if (this.currentThreadId) {
|
|
523
658
|
this.currentThread = this.codex.resumeThread(this.currentThreadId, {
|
|
@@ -559,7 +694,6 @@ var CodexManager = class {
|
|
|
559
694
|
}
|
|
560
695
|
}
|
|
561
696
|
} finally {
|
|
562
|
-
this.processing = false;
|
|
563
697
|
if (linearSessionId) {
|
|
564
698
|
const status = getGitStatus(this.workingDirectory);
|
|
565
699
|
monolithService.sendEvent({ type: "agent_turn_complete", payload: { linearSessionId, status } }).catch(() => {
|
|
@@ -602,7 +736,7 @@ var CodexManager = class {
|
|
|
602
736
|
async reset() {
|
|
603
737
|
this.currentThread = null;
|
|
604
738
|
this.currentThreadId = null;
|
|
605
|
-
this.
|
|
739
|
+
this.messageQueue.reset();
|
|
606
740
|
}
|
|
607
741
|
getThreadId() {
|
|
608
742
|
return this.currentThreadId;
|
|
@@ -684,21 +818,15 @@ codex.post("/send", async (c) => {
|
|
|
684
818
|
if (!message || typeof message !== "string") {
|
|
685
819
|
return c.json({ error: "Message is required and must be a string" }, 400);
|
|
686
820
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
{
|
|
690
|
-
error: "A turn is already in progress. Please wait."
|
|
691
|
-
},
|
|
692
|
-
400
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
|
-
codexManager.sendMessage(message, model, customInstructions).catch((error) => {
|
|
696
|
-
console.error("[Codex Route] Error in background message processing:", error);
|
|
697
|
-
});
|
|
698
|
-
return c.json({
|
|
821
|
+
const result = await codexManager.enqueueMessage(message, model, customInstructions);
|
|
822
|
+
const response = {
|
|
699
823
|
success: true,
|
|
700
|
-
message: "Message sent successfully"
|
|
701
|
-
|
|
824
|
+
message: result.queued ? `Message queued at position ${result.position}` : "Message sent successfully",
|
|
825
|
+
queued: result.queued,
|
|
826
|
+
messageId: result.messageId,
|
|
827
|
+
position: result.position
|
|
828
|
+
};
|
|
829
|
+
return c.json(response);
|
|
702
830
|
} catch (error) {
|
|
703
831
|
console.error("Error in /codex/send:", error);
|
|
704
832
|
return c.json(
|
|
@@ -766,6 +894,21 @@ codex.get("/status", async (c) => {
|
|
|
766
894
|
);
|
|
767
895
|
}
|
|
768
896
|
});
|
|
897
|
+
codex.get("/queue", async (c) => {
|
|
898
|
+
try {
|
|
899
|
+
const queueStatus = codexManager.getQueueStatus();
|
|
900
|
+
return c.json(queueStatus);
|
|
901
|
+
} catch (error) {
|
|
902
|
+
console.error("Error in /codex/queue:", error);
|
|
903
|
+
return c.json(
|
|
904
|
+
{
|
|
905
|
+
error: "Failed to retrieve queue status",
|
|
906
|
+
details: error instanceof Error ? error.message : "Unknown error"
|
|
907
|
+
},
|
|
908
|
+
500
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
});
|
|
769
912
|
codex.post("/reset", async (c) => {
|
|
770
913
|
try {
|
|
771
914
|
await codexManager.reset();
|
|
@@ -801,7 +944,7 @@ var ClaudeManager = class {
|
|
|
801
944
|
historyFile;
|
|
802
945
|
sessionId = null;
|
|
803
946
|
initialized;
|
|
804
|
-
|
|
947
|
+
messageQueue;
|
|
805
948
|
constructor(workingDirectory) {
|
|
806
949
|
if (workingDirectory) {
|
|
807
950
|
this.workingDirectory = workingDirectory;
|
|
@@ -816,17 +959,42 @@ var ClaudeManager = class {
|
|
|
816
959
|
}
|
|
817
960
|
this.historyFile = join2(homedir2(), ".replicas", "claude", "history.jsonl");
|
|
818
961
|
this.initialized = this.initialize();
|
|
962
|
+
this.messageQueue = new MessageQueue(this.processMessageInternal.bind(this));
|
|
819
963
|
}
|
|
820
964
|
isProcessing() {
|
|
821
|
-
return this.
|
|
965
|
+
return this.messageQueue.isProcessing();
|
|
822
966
|
}
|
|
967
|
+
/**
|
|
968
|
+
* Enqueue a message for processing. If not currently processing, starts immediately.
|
|
969
|
+
* If already processing, adds to queue.
|
|
970
|
+
* @returns Object with queued status, messageId, and position in queue
|
|
971
|
+
*/
|
|
972
|
+
async enqueueMessage(message, model, customInstructions) {
|
|
973
|
+
await this.initialized;
|
|
974
|
+
return this.messageQueue.enqueue(message, model, customInstructions);
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Get the current queue status
|
|
978
|
+
*/
|
|
979
|
+
getQueueStatus() {
|
|
980
|
+
return this.messageQueue.getStatus();
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Legacy sendMessage method - now uses the queue internally
|
|
984
|
+
* @deprecated Use enqueueMessage for better control over queue status
|
|
985
|
+
*/
|
|
823
986
|
async sendMessage(message, model, customInstructions) {
|
|
987
|
+
await this.enqueueMessage(message, model, customInstructions);
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Internal method that actually processes the message
|
|
991
|
+
*/
|
|
992
|
+
async processMessageInternal(message, model, customInstructions) {
|
|
824
993
|
const linearSessionId = process.env.LINEAR_SESSION_ID;
|
|
825
994
|
if (!message || !message.trim()) {
|
|
826
995
|
throw new Error("Message cannot be empty");
|
|
827
996
|
}
|
|
828
997
|
await this.initialized;
|
|
829
|
-
this.processing = true;
|
|
830
998
|
try {
|
|
831
999
|
const userMessage = {
|
|
832
1000
|
type: "user",
|
|
@@ -874,7 +1042,6 @@ var ClaudeManager = class {
|
|
|
874
1042
|
}
|
|
875
1043
|
}
|
|
876
1044
|
} finally {
|
|
877
|
-
this.processing = false;
|
|
878
1045
|
if (linearSessionId) {
|
|
879
1046
|
const status = getGitStatus(this.workingDirectory);
|
|
880
1047
|
monolithService.sendEvent({ type: "agent_turn_complete", payload: { linearSessionId, status } }).catch(() => {
|
|
@@ -893,7 +1060,7 @@ var ClaudeManager = class {
|
|
|
893
1060
|
async getStatus() {
|
|
894
1061
|
await this.initialized;
|
|
895
1062
|
const status = {
|
|
896
|
-
has_active_thread: this.
|
|
1063
|
+
has_active_thread: this.messageQueue.isProcessing(),
|
|
897
1064
|
thread_id: this.sessionId,
|
|
898
1065
|
working_directory: this.workingDirectory
|
|
899
1066
|
};
|
|
@@ -903,13 +1070,13 @@ var ClaudeManager = class {
|
|
|
903
1070
|
await this.initialized;
|
|
904
1071
|
const allEvents = await readJSONL(this.historyFile);
|
|
905
1072
|
const events = allEvents.filter((event) => event.timestamp > since);
|
|
906
|
-
const isComplete = !this.
|
|
1073
|
+
const isComplete = !this.messageQueue.isProcessing();
|
|
907
1074
|
return { events, isComplete };
|
|
908
1075
|
}
|
|
909
1076
|
async reset() {
|
|
910
1077
|
await this.initialized;
|
|
911
1078
|
this.sessionId = null;
|
|
912
|
-
this.
|
|
1079
|
+
this.messageQueue.reset();
|
|
913
1080
|
try {
|
|
914
1081
|
await rm(this.historyFile, { force: true });
|
|
915
1082
|
} catch {
|
|
@@ -946,21 +1113,15 @@ claude.post("/send", async (c) => {
|
|
|
946
1113
|
if (!message || typeof message !== "string") {
|
|
947
1114
|
return c.json({ error: "Message is required and must be a string" }, 400);
|
|
948
1115
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
{
|
|
952
|
-
error: "A turn is already in progress. Please wait."
|
|
953
|
-
},
|
|
954
|
-
400
|
|
955
|
-
);
|
|
956
|
-
}
|
|
957
|
-
claudeManager.sendMessage(message, model, customInstructions).catch((error) => {
|
|
958
|
-
console.error("[Claude Route] Error in background message processing:", error);
|
|
959
|
-
});
|
|
960
|
-
return c.json({
|
|
1116
|
+
const result = await claudeManager.enqueueMessage(message, model, customInstructions);
|
|
1117
|
+
const response = {
|
|
961
1118
|
success: true,
|
|
962
|
-
message: "Message sent successfully"
|
|
963
|
-
|
|
1119
|
+
message: result.queued ? `Message queued at position ${result.position}` : "Message sent successfully",
|
|
1120
|
+
queued: result.queued,
|
|
1121
|
+
messageId: result.messageId,
|
|
1122
|
+
position: result.position
|
|
1123
|
+
};
|
|
1124
|
+
return c.json(response);
|
|
964
1125
|
} catch (error) {
|
|
965
1126
|
console.error("Error in /claude/send:", error);
|
|
966
1127
|
return c.json(
|
|
@@ -1028,6 +1189,21 @@ claude.get("/status", async (c) => {
|
|
|
1028
1189
|
);
|
|
1029
1190
|
}
|
|
1030
1191
|
});
|
|
1192
|
+
claude.get("/queue", async (c) => {
|
|
1193
|
+
try {
|
|
1194
|
+
const queueStatus = claudeManager.getQueueStatus();
|
|
1195
|
+
return c.json(queueStatus);
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
console.error("Error in /claude/queue:", error);
|
|
1198
|
+
return c.json(
|
|
1199
|
+
{
|
|
1200
|
+
error: "Failed to retrieve queue status",
|
|
1201
|
+
details: error instanceof Error ? error.message : "Unknown error"
|
|
1202
|
+
},
|
|
1203
|
+
500
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1031
1207
|
claude.post("/reset", async (c) => {
|
|
1032
1208
|
try {
|
|
1033
1209
|
await claudeManager.reset();
|
|
@@ -1128,9 +1304,186 @@ var GitHubTokenManager = class {
|
|
|
1128
1304
|
};
|
|
1129
1305
|
var githubTokenManager = new GitHubTokenManager();
|
|
1130
1306
|
|
|
1307
|
+
// src/services/claude-token-manager.ts
|
|
1308
|
+
import { promises as fs2 } from "fs";
|
|
1309
|
+
import path2 from "path";
|
|
1310
|
+
var ClaudeTokenManager = class {
|
|
1311
|
+
refreshInterval = null;
|
|
1312
|
+
REFRESH_INTERVAL_MS = 45 * 60 * 1e3;
|
|
1313
|
+
// 45 minutes
|
|
1314
|
+
async start() {
|
|
1315
|
+
const monolithUrl = process.env.MONOLITH_URL;
|
|
1316
|
+
const workspaceId = process.env.WORKSPACE_ID;
|
|
1317
|
+
const engineSecret = process.env.REPLICAS_ENGINE_SECRET;
|
|
1318
|
+
if (!monolithUrl || !workspaceId || !engineSecret) {
|
|
1319
|
+
console.log("[ClaudeTokenManager] Skipping: missing MONOLITH_URL, WORKSPACE_ID, or REPLICAS_ENGINE_SECRET");
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
console.log("[ClaudeTokenManager] Starting token refresh service");
|
|
1323
|
+
await this.refreshCredentials();
|
|
1324
|
+
this.refreshInterval = setInterval(() => {
|
|
1325
|
+
this.refreshCredentials().catch((error) => {
|
|
1326
|
+
console.error("[ClaudeTokenManager] Scheduled refresh failed:", error);
|
|
1327
|
+
});
|
|
1328
|
+
}, this.REFRESH_INTERVAL_MS);
|
|
1329
|
+
console.log("[ClaudeTokenManager] Token refresh scheduled every 45 minutes");
|
|
1330
|
+
}
|
|
1331
|
+
stop() {
|
|
1332
|
+
if (this.refreshInterval) {
|
|
1333
|
+
clearInterval(this.refreshInterval);
|
|
1334
|
+
this.refreshInterval = null;
|
|
1335
|
+
console.log("[ClaudeTokenManager] Stopped");
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
async refreshCredentials() {
|
|
1339
|
+
const monolithUrl = process.env.MONOLITH_URL;
|
|
1340
|
+
const workspaceId = process.env.WORKSPACE_ID;
|
|
1341
|
+
const engineSecret = process.env.REPLICAS_ENGINE_SECRET;
|
|
1342
|
+
if (!monolithUrl || !workspaceId || !engineSecret) {
|
|
1343
|
+
console.log("[ClaudeTokenManager] Refresh skipped: missing configuration");
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
console.log("[ClaudeTokenManager] Refreshing Claude credentials...");
|
|
1347
|
+
try {
|
|
1348
|
+
const response = await fetch(`${monolithUrl}/v1/engine/claude/refresh-credentials`, {
|
|
1349
|
+
method: "POST",
|
|
1350
|
+
headers: {
|
|
1351
|
+
"Authorization": `Bearer ${engineSecret}`,
|
|
1352
|
+
"X-Workspace-Id": workspaceId,
|
|
1353
|
+
"Content-Type": "application/json"
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
if (!response.ok) {
|
|
1357
|
+
const errorText = await response.text();
|
|
1358
|
+
throw new Error(`Credentials refresh failed: ${response.status} ${errorText}`);
|
|
1359
|
+
}
|
|
1360
|
+
const data = await response.json();
|
|
1361
|
+
await this.updateClaudeCredentials(data);
|
|
1362
|
+
console.log(`[ClaudeTokenManager] Credentials refreshed successfully, expires at ${data.expiresAt}`);
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
console.error("[ClaudeTokenManager] Failed to refresh credentials:", error);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
async updateClaudeCredentials(credentials) {
|
|
1368
|
+
const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME;
|
|
1369
|
+
if (!workspaceHome) {
|
|
1370
|
+
console.warn("[ClaudeTokenManager] No WORKSPACE_HOME or HOME set, skipping credentials update");
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
const claudeDir = path2.join(workspaceHome, ".claude");
|
|
1374
|
+
const credentialsPath = path2.join(claudeDir, ".credentials.json");
|
|
1375
|
+
const claudeCliConfig = {
|
|
1376
|
+
claudeAiOauth: {
|
|
1377
|
+
accessToken: credentials.accessToken,
|
|
1378
|
+
refreshToken: credentials.refreshToken,
|
|
1379
|
+
expiresAt: new Date(credentials.expiresAt).getTime(),
|
|
1380
|
+
scopes: credentials.scopes,
|
|
1381
|
+
subscriptionType: credentials.subscriptionType
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
try {
|
|
1385
|
+
await fs2.mkdir(claudeDir, { recursive: true, mode: 448 });
|
|
1386
|
+
await fs2.writeFile(credentialsPath, JSON.stringify(claudeCliConfig, null, 2), { mode: 384 });
|
|
1387
|
+
console.log(`[ClaudeTokenManager] Updated ${credentialsPath}`);
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
console.error("[ClaudeTokenManager] Failed to update credentials file:", error);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
var claudeTokenManager = new ClaudeTokenManager();
|
|
1394
|
+
|
|
1395
|
+
// src/services/codex-token-manager.ts
|
|
1396
|
+
import { promises as fs3 } from "fs";
|
|
1397
|
+
import path3 from "path";
|
|
1398
|
+
var CodexTokenManager = class {
|
|
1399
|
+
refreshInterval = null;
|
|
1400
|
+
REFRESH_INTERVAL_MS = 45 * 60 * 1e3;
|
|
1401
|
+
// 45 minutes
|
|
1402
|
+
async start() {
|
|
1403
|
+
const monolithUrl = process.env.MONOLITH_URL;
|
|
1404
|
+
const workspaceId = process.env.WORKSPACE_ID;
|
|
1405
|
+
const engineSecret = process.env.REPLICAS_ENGINE_SECRET;
|
|
1406
|
+
if (!monolithUrl || !workspaceId || !engineSecret) {
|
|
1407
|
+
console.log("[CodexTokenManager] Skipping: missing MONOLITH_URL, WORKSPACE_ID, or REPLICAS_ENGINE_SECRET");
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
console.log("[CodexTokenManager] Starting token refresh service");
|
|
1411
|
+
await this.refreshCredentials();
|
|
1412
|
+
this.refreshInterval = setInterval(() => {
|
|
1413
|
+
this.refreshCredentials().catch((error) => {
|
|
1414
|
+
console.error("[CodexTokenManager] Scheduled refresh failed:", error);
|
|
1415
|
+
});
|
|
1416
|
+
}, this.REFRESH_INTERVAL_MS);
|
|
1417
|
+
console.log("[CodexTokenManager] Token refresh scheduled every 45 minutes");
|
|
1418
|
+
}
|
|
1419
|
+
stop() {
|
|
1420
|
+
if (this.refreshInterval) {
|
|
1421
|
+
clearInterval(this.refreshInterval);
|
|
1422
|
+
this.refreshInterval = null;
|
|
1423
|
+
console.log("[CodexTokenManager] Stopped");
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
async refreshCredentials() {
|
|
1427
|
+
const monolithUrl = process.env.MONOLITH_URL;
|
|
1428
|
+
const workspaceId = process.env.WORKSPACE_ID;
|
|
1429
|
+
const engineSecret = process.env.REPLICAS_ENGINE_SECRET;
|
|
1430
|
+
if (!monolithUrl || !workspaceId || !engineSecret) {
|
|
1431
|
+
console.log("[CodexTokenManager] Refresh skipped: missing configuration");
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
console.log("[CodexTokenManager] Refreshing Codex credentials...");
|
|
1435
|
+
try {
|
|
1436
|
+
const response = await fetch(`${monolithUrl}/v1/engine/codex/refresh-credentials`, {
|
|
1437
|
+
method: "POST",
|
|
1438
|
+
headers: {
|
|
1439
|
+
"Authorization": `Bearer ${engineSecret}`,
|
|
1440
|
+
"X-Workspace-Id": workspaceId,
|
|
1441
|
+
"Content-Type": "application/json"
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
if (!response.ok) {
|
|
1445
|
+
const errorText = await response.text();
|
|
1446
|
+
throw new Error(`Credentials refresh failed: ${response.status} ${errorText}`);
|
|
1447
|
+
}
|
|
1448
|
+
const data = await response.json();
|
|
1449
|
+
await this.updateCodexCredentials(data);
|
|
1450
|
+
console.log(`[CodexTokenManager] Credentials refreshed successfully, expires at ${data.expiresAt}`);
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
console.error("[CodexTokenManager] Failed to refresh credentials:", error);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
async updateCodexCredentials(credentials) {
|
|
1456
|
+
const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME;
|
|
1457
|
+
if (!workspaceHome) {
|
|
1458
|
+
console.warn("[CodexTokenManager] No WORKSPACE_HOME or HOME set, skipping credentials update");
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
const codexDir = path3.join(workspaceHome, ".codex");
|
|
1462
|
+
const authPath = path3.join(codexDir, "auth.json");
|
|
1463
|
+
const codexAuthConfig = {
|
|
1464
|
+
OPENAI_API_KEY: null,
|
|
1465
|
+
tokens: {
|
|
1466
|
+
id_token: credentials.idToken,
|
|
1467
|
+
access_token: credentials.accessToken,
|
|
1468
|
+
refresh_token: credentials.refreshToken,
|
|
1469
|
+
account_id: credentials.accountId
|
|
1470
|
+
},
|
|
1471
|
+
last_refresh: (/* @__PURE__ */ new Date()).toISOString()
|
|
1472
|
+
};
|
|
1473
|
+
try {
|
|
1474
|
+
await fs3.mkdir(codexDir, { recursive: true, mode: 448 });
|
|
1475
|
+
await fs3.writeFile(authPath, JSON.stringify(codexAuthConfig, null, 2), { mode: 384 });
|
|
1476
|
+
console.log(`[CodexTokenManager] Updated ${authPath}`);
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
console.error("[CodexTokenManager] Failed to update credentials file:", error);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
};
|
|
1482
|
+
var codexTokenManager = new CodexTokenManager();
|
|
1483
|
+
|
|
1131
1484
|
// src/services/git-init.ts
|
|
1132
1485
|
import { existsSync } from "fs";
|
|
1133
|
-
import
|
|
1486
|
+
import path4 from "path";
|
|
1134
1487
|
var initializedBranch = null;
|
|
1135
1488
|
function findAvailableBranchName(baseName, cwd) {
|
|
1136
1489
|
if (!branchExists(baseName, cwd)) {
|
|
@@ -1164,7 +1517,7 @@ async function initializeGitRepository() {
|
|
|
1164
1517
|
error: "No WORKSPACE_NAME environment variable set"
|
|
1165
1518
|
};
|
|
1166
1519
|
}
|
|
1167
|
-
const repoPath =
|
|
1520
|
+
const repoPath = path4.join(workspaceHome, "workspaces", repoName);
|
|
1168
1521
|
if (!existsSync(repoPath)) {
|
|
1169
1522
|
console.log(`[GitInit] Repository directory does not exist: ${repoPath}`);
|
|
1170
1523
|
console.log("[GitInit] Waiting for initializer to clone the repository...");
|
|
@@ -1173,7 +1526,7 @@ async function initializeGitRepository() {
|
|
|
1173
1526
|
branch: null
|
|
1174
1527
|
};
|
|
1175
1528
|
}
|
|
1176
|
-
if (!existsSync(
|
|
1529
|
+
if (!existsSync(path4.join(repoPath, ".git"))) {
|
|
1177
1530
|
return {
|
|
1178
1531
|
success: false,
|
|
1179
1532
|
branch: null,
|
|
@@ -1296,6 +1649,8 @@ serve(
|
|
|
1296
1649
|
console.warn(`Git initialization warning: ${gitResult.error}`);
|
|
1297
1650
|
}
|
|
1298
1651
|
await githubTokenManager.start();
|
|
1652
|
+
await claudeTokenManager.start();
|
|
1653
|
+
await codexTokenManager.start();
|
|
1299
1654
|
const monolithService2 = new MonolithService();
|
|
1300
1655
|
await monolithService2.sendEvent({ type: "workspace_ready", payload: {} });
|
|
1301
1656
|
}
|