@groupchatai/claude-runner 0.3.1 → 0.4.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.
- package/dist/index.js +88 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -445,8 +445,10 @@ function runShellCommand(cmd, args, cwd) {
|
|
|
445
445
|
});
|
|
446
446
|
}
|
|
447
447
|
var sessionCache = /* @__PURE__ */ new Map();
|
|
448
|
+
var runCounter = 0;
|
|
448
449
|
async function processRun(client, run, config) {
|
|
449
|
-
const
|
|
450
|
+
const runNum = ++runCounter;
|
|
451
|
+
const runTag = ` ${C.pid}[${runNum}]${C.reset}`;
|
|
450
452
|
const log = (msg) => console.log(`${runTag} ${msg}`);
|
|
451
453
|
const logGreen = (msg) => console.log(`${runTag} ${C.green}${msg}${C.reset}`);
|
|
452
454
|
const detail = await client.getRunDetail(run.id);
|
|
@@ -638,23 +640,89 @@ function parseArgs() {
|
|
|
638
640
|
async function sleep(ms) {
|
|
639
641
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
640
642
|
}
|
|
641
|
-
|
|
643
|
+
var TaskScheduler = class {
|
|
644
|
+
slots = /* @__PURE__ */ new Map();
|
|
645
|
+
processing = /* @__PURE__ */ new Set();
|
|
646
|
+
get activeTaskCount() {
|
|
647
|
+
return this.slots.size;
|
|
648
|
+
}
|
|
649
|
+
isRunActive(runId) {
|
|
650
|
+
return this.processing.has(runId);
|
|
651
|
+
}
|
|
652
|
+
hasActiveTask(taskId) {
|
|
653
|
+
return this.slots.has(taskId);
|
|
654
|
+
}
|
|
655
|
+
tryStart(run, maxConcurrent) {
|
|
656
|
+
if (this.processing.has(run.id)) return "at_limit";
|
|
657
|
+
const existingSlot = this.slots.get(run.taskId);
|
|
658
|
+
if (existingSlot) {
|
|
659
|
+
if (!existingSlot.queue.some((q) => q.id === run.id)) {
|
|
660
|
+
existingSlot.queue.push(run);
|
|
661
|
+
}
|
|
662
|
+
return "queued";
|
|
663
|
+
}
|
|
664
|
+
if (this.slots.size >= maxConcurrent) {
|
|
665
|
+
return "at_limit";
|
|
666
|
+
}
|
|
667
|
+
this.slots.set(run.taskId, { activeRunId: run.id, queue: [] });
|
|
668
|
+
this.processing.add(run.id);
|
|
669
|
+
return "start";
|
|
670
|
+
}
|
|
671
|
+
complete(run) {
|
|
672
|
+
this.processing.delete(run.id);
|
|
673
|
+
const slot = this.slots.get(run.taskId);
|
|
674
|
+
if (!slot) return void 0;
|
|
675
|
+
const next = slot.queue.shift();
|
|
676
|
+
if (next) {
|
|
677
|
+
slot.activeRunId = next.id;
|
|
678
|
+
this.processing.add(next.id);
|
|
679
|
+
return next;
|
|
680
|
+
}
|
|
681
|
+
this.slots.delete(run.taskId);
|
|
682
|
+
return void 0;
|
|
683
|
+
}
|
|
684
|
+
isEmpty() {
|
|
685
|
+
return this.slots.size === 0;
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
function handlePendingRuns(runs, scheduler, client, config) {
|
|
642
689
|
if (runs.length > 0 && config.verbose) {
|
|
643
690
|
console.log(`\u{1F4EC} ${runs.length} pending run(s)`);
|
|
644
691
|
}
|
|
645
692
|
for (const run of runs) {
|
|
646
|
-
|
|
647
|
-
if (
|
|
693
|
+
const result = scheduler.tryStart(run, config.maxConcurrent);
|
|
694
|
+
if (result === "at_limit") {
|
|
648
695
|
if (config.verbose) {
|
|
649
|
-
console.log(`\u23F8 At concurrency limit (${config.maxConcurrent}), waiting\u2026`);
|
|
696
|
+
console.log(`\u23F8 At concurrency limit (${config.maxConcurrent} tasks), waiting\u2026`);
|
|
650
697
|
}
|
|
651
698
|
break;
|
|
652
699
|
}
|
|
653
|
-
|
|
654
|
-
|
|
700
|
+
if (result === "queued") {
|
|
701
|
+
if (config.verbose) {
|
|
702
|
+
console.log(` ${C.dim}Queued follow-up for active task${C.reset}`);
|
|
703
|
+
}
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
processRunWithDrain(client, run, scheduler, config).catch(
|
|
707
|
+
(err) => console.error(`Unhandled error processing run ${run.id}:`, err)
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
async function processRunWithDrain(client, run, scheduler, config) {
|
|
712
|
+
let current = run;
|
|
713
|
+
while (current) {
|
|
714
|
+
try {
|
|
715
|
+
await processRun(client, current, config);
|
|
716
|
+
} catch (err) {
|
|
717
|
+
console.error(`Unhandled error processing run ${current.id}:`, err);
|
|
718
|
+
}
|
|
719
|
+
current = scheduler.complete(current);
|
|
720
|
+
if (current) {
|
|
721
|
+
console.log(` \u23ED Processing queued follow-up\u2026`);
|
|
722
|
+
}
|
|
655
723
|
}
|
|
656
724
|
}
|
|
657
|
-
async function runWithWebSocket(client, config,
|
|
725
|
+
async function runWithWebSocket(client, config, scheduler) {
|
|
658
726
|
let ConvexClient;
|
|
659
727
|
let anyApi;
|
|
660
728
|
try {
|
|
@@ -663,7 +731,7 @@ async function runWithWebSocket(client, config, activeRuns) {
|
|
|
663
731
|
} catch {
|
|
664
732
|
console.warn("\u26A0 convex package not found \u2014 falling back to HTTP polling.");
|
|
665
733
|
console.warn(" Install convex for WebSocket mode: npm i convex\n");
|
|
666
|
-
await runWithPolling(client, config,
|
|
734
|
+
await runWithPolling(client, config, scheduler);
|
|
667
735
|
return;
|
|
668
736
|
}
|
|
669
737
|
const convex = new ConvexClient(config.convexUrl);
|
|
@@ -673,7 +741,7 @@ async function runWithWebSocket(client, config, activeRuns) {
|
|
|
673
741
|
{ token: config.token },
|
|
674
742
|
(runs) => {
|
|
675
743
|
if (!runs || runs.length === 0) return;
|
|
676
|
-
handlePendingRuns(runs,
|
|
744
|
+
handlePendingRuns(runs, scheduler, client, config);
|
|
677
745
|
}
|
|
678
746
|
);
|
|
679
747
|
await new Promise((resolve) => {
|
|
@@ -686,7 +754,7 @@ async function runWithWebSocket(client, config, activeRuns) {
|
|
|
686
754
|
process.on("SIGTERM", shutdown);
|
|
687
755
|
});
|
|
688
756
|
}
|
|
689
|
-
async function runWithPolling(client, config,
|
|
757
|
+
async function runWithPolling(client, config, scheduler) {
|
|
690
758
|
console.log(`\u{1F4E1} Polling every ${config.pollInterval}ms \u2014 listening for tasks\u2026
|
|
691
759
|
`);
|
|
692
760
|
let running = true;
|
|
@@ -699,17 +767,17 @@ async function runWithPolling(client, config, activeRuns) {
|
|
|
699
767
|
if (config.once) {
|
|
700
768
|
try {
|
|
701
769
|
const pending = await client.listPendingRuns();
|
|
702
|
-
handlePendingRuns(pending,
|
|
770
|
+
handlePendingRuns(pending, scheduler, client, config);
|
|
703
771
|
} catch (err) {
|
|
704
772
|
console.error(`Poll error: ${err instanceof Error ? err.message : err}`);
|
|
705
773
|
}
|
|
706
|
-
while (
|
|
774
|
+
while (!scheduler.isEmpty()) await sleep(1e3);
|
|
707
775
|
return;
|
|
708
776
|
}
|
|
709
777
|
while (running) {
|
|
710
778
|
try {
|
|
711
779
|
const pending = await client.listPendingRuns();
|
|
712
|
-
handlePendingRuns(pending,
|
|
780
|
+
handlePendingRuns(pending, scheduler, client, config);
|
|
713
781
|
} catch (err) {
|
|
714
782
|
const msg = err instanceof Error ? err.message : String(err);
|
|
715
783
|
if (config.verbose || !msg.includes("fetch")) {
|
|
@@ -738,15 +806,15 @@ async function main() {
|
|
|
738
806
|
if (config.model) console.log(` Model: ${config.model}`);
|
|
739
807
|
if (config.dryRun) console.log(` Mode: DRY RUN`);
|
|
740
808
|
console.log();
|
|
741
|
-
const
|
|
809
|
+
const scheduler = new TaskScheduler();
|
|
742
810
|
if (config.poll || config.once) {
|
|
743
|
-
await runWithPolling(client, config,
|
|
811
|
+
await runWithPolling(client, config, scheduler);
|
|
744
812
|
} else {
|
|
745
|
-
await runWithWebSocket(client, config,
|
|
813
|
+
await runWithWebSocket(client, config, scheduler);
|
|
746
814
|
}
|
|
747
|
-
if (
|
|
748
|
-
console.log(`\u23F3 Waiting for ${
|
|
749
|
-
while (
|
|
815
|
+
if (!scheduler.isEmpty()) {
|
|
816
|
+
console.log(`\u23F3 Waiting for ${scheduler.activeTaskCount} in-flight task(s)\u2026`);
|
|
817
|
+
while (!scheduler.isEmpty()) {
|
|
750
818
|
await sleep(1e3);
|
|
751
819
|
}
|
|
752
820
|
}
|