@harness-engineering/orchestrator 0.2.8 → 0.2.10
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.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +44 -12
- package/dist/index.mjs +44 -12
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -94,7 +94,14 @@ interface OrchestratorState {
|
|
|
94
94
|
running: Map<string, RunningEntry>;
|
|
95
95
|
claimed: Set<string>;
|
|
96
96
|
retryAttempts: Map<string, RetryEntry>;
|
|
97
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Tracks completed issue IDs mapped to the epoch-ms timestamp when the
|
|
99
|
+
* completion was recorded. The timestamp enables a grace-period so that
|
|
100
|
+
* issues manually re-activated in the roadmap can be re-dispatched, while
|
|
101
|
+
* still guarding against duplicate dispatch on the tick immediately after
|
|
102
|
+
* completion (when the tracker write-back may not have persisted yet).
|
|
103
|
+
*/
|
|
104
|
+
completed: Map<string, number>;
|
|
98
105
|
tokenTotals: TokenTotals;
|
|
99
106
|
rateLimits: RateLimitSnapshot$1;
|
|
100
107
|
/** Running count of claim rejections (another orchestrator won the race). */
|
package/dist/index.d.ts
CHANGED
|
@@ -94,7 +94,14 @@ interface OrchestratorState {
|
|
|
94
94
|
running: Map<string, RunningEntry>;
|
|
95
95
|
claimed: Set<string>;
|
|
96
96
|
retryAttempts: Map<string, RetryEntry>;
|
|
97
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Tracks completed issue IDs mapped to the epoch-ms timestamp when the
|
|
99
|
+
* completion was recorded. The timestamp enables a grace-period so that
|
|
100
|
+
* issues manually re-activated in the roadmap can be re-dispatched, while
|
|
101
|
+
* still guarding against duplicate dispatch on the tick immediately after
|
|
102
|
+
* completion (when the tracker write-back may not have persisted yet).
|
|
103
|
+
*/
|
|
104
|
+
completed: Map<string, number>;
|
|
98
105
|
tokenTotals: TokenTotals;
|
|
99
106
|
rateLimits: RateLimitSnapshot$1;
|
|
100
107
|
/** Running count of claim rejections (another orchestrator won the race). */
|
package/dist/index.js
CHANGED
|
@@ -492,7 +492,7 @@ function cloneState(state) {
|
|
|
492
492
|
running: new Map(state.running),
|
|
493
493
|
claimed: new Set(state.claimed),
|
|
494
494
|
retryAttempts: new Map(state.retryAttempts),
|
|
495
|
-
completed: new
|
|
495
|
+
completed: new Map(state.completed),
|
|
496
496
|
tokenTotals: { ...state.tokenTotals },
|
|
497
497
|
rateLimits: { ...state.rateLimits }
|
|
498
498
|
};
|
|
@@ -591,13 +591,39 @@ function resolveBackend(action, hasLocalBackend) {
|
|
|
591
591
|
}
|
|
592
592
|
function pruneCompleted(next) {
|
|
593
593
|
if (next.completed.size <= COMPLETED_PRUNE_THRESHOLD) return;
|
|
594
|
-
for (const id of next.completed) {
|
|
594
|
+
for (const [id] of next.completed) {
|
|
595
595
|
const hasPending = next.retryAttempts.has(id) || next.running.has(id) || next.claimed.has(id);
|
|
596
596
|
if (!hasPending) {
|
|
597
597
|
next.completed.delete(id);
|
|
598
598
|
}
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
|
+
var COMPLETED_GRACE_MULTIPLIER = 2;
|
|
602
|
+
function reconcileCompletedAndClaimed(next, candidates, nowMs, effects) {
|
|
603
|
+
const gracePeriodMs = next.pollIntervalMs * COMPLETED_GRACE_MULTIPLIER;
|
|
604
|
+
const candidateIds = new Set(candidates.map((c) => c.id));
|
|
605
|
+
for (const [id, completedAtMs] of next.completed) {
|
|
606
|
+
if (candidateIds.has(id) && nowMs - completedAtMs > gracePeriodMs) {
|
|
607
|
+
next.completed.delete(id);
|
|
608
|
+
effects.push({
|
|
609
|
+
type: "emitLog",
|
|
610
|
+
level: "info",
|
|
611
|
+
message: `Released completed lock for ${id}: reappeared as active candidate after grace period`
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
for (const id of next.claimed) {
|
|
616
|
+
if (next.running.has(id) || next.retryAttempts.has(id)) continue;
|
|
617
|
+
if (!candidateIds.has(id)) {
|
|
618
|
+
next.claimed.delete(id);
|
|
619
|
+
effects.push({
|
|
620
|
+
type: "emitLog",
|
|
621
|
+
level: "info",
|
|
622
|
+
message: `Released orphaned claim for ${id}: no longer in active candidates`
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
601
627
|
function handleTick(state, event, config) {
|
|
602
628
|
const { candidates, runningStates, nowMs } = event;
|
|
603
629
|
const next = cloneState(state);
|
|
@@ -610,6 +636,7 @@ function handleTick(state, event, config) {
|
|
|
610
636
|
);
|
|
611
637
|
effects.push(...reconcileEffects);
|
|
612
638
|
applyReconcileEffects(next, reconcileEffects);
|
|
639
|
+
reconcileCompletedAndClaimed(next, candidates, nowMs, effects);
|
|
613
640
|
const eligible = selectCandidates(
|
|
614
641
|
candidates,
|
|
615
642
|
next,
|
|
@@ -671,7 +698,7 @@ function handleWorkerExit(state, issueId, reason, error, attempt, config) {
|
|
|
671
698
|
next.running.delete(issueId);
|
|
672
699
|
const nowMs = Date.now();
|
|
673
700
|
if (reason === "normal") {
|
|
674
|
-
next.completed.
|
|
701
|
+
next.completed.set(issueId, nowMs);
|
|
675
702
|
next.claimed.delete(issueId);
|
|
676
703
|
effects.push({
|
|
677
704
|
type: "cleanWorkspace",
|
|
@@ -948,7 +975,7 @@ function createEmptyState(config) {
|
|
|
948
975
|
running: /* @__PURE__ */ new Map(),
|
|
949
976
|
claimed: /* @__PURE__ */ new Set(),
|
|
950
977
|
retryAttempts: /* @__PURE__ */ new Map(),
|
|
951
|
-
completed: /* @__PURE__ */ new
|
|
978
|
+
completed: /* @__PURE__ */ new Map(),
|
|
952
979
|
tokenTotals: {
|
|
953
980
|
inputTokens: 0,
|
|
954
981
|
outputTokens: 0,
|
|
@@ -1414,7 +1441,7 @@ var WorkflowLoader = class {
|
|
|
1414
1441
|
if (parts.length < 3) {
|
|
1415
1442
|
return (0, import_types3.Err)(
|
|
1416
1443
|
new Error(
|
|
1417
|
-
`Invalid
|
|
1444
|
+
`Invalid harness.orchestrator.md format at ${filePath}. Expected frontmatter surrounded by '---'.`
|
|
1418
1445
|
)
|
|
1419
1446
|
);
|
|
1420
1447
|
}
|
|
@@ -1701,11 +1728,6 @@ var WorkspaceManager = class {
|
|
|
1701
1728
|
const workspacePath = path5.resolve(this.resolvePath(identifier));
|
|
1702
1729
|
try {
|
|
1703
1730
|
await fs7.access(path5.join(workspacePath, ".git"));
|
|
1704
|
-
return (0, import_types6.Ok)(workspacePath);
|
|
1705
|
-
} catch {
|
|
1706
|
-
}
|
|
1707
|
-
try {
|
|
1708
|
-
await fs7.access(workspacePath);
|
|
1709
1731
|
const repoRoot2 = await this.getRepoRoot();
|
|
1710
1732
|
try {
|
|
1711
1733
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
|
|
@@ -1713,6 +1735,16 @@ var WorkspaceManager = class {
|
|
|
1713
1735
|
await fs7.rm(workspacePath, { recursive: true, force: true });
|
|
1714
1736
|
}
|
|
1715
1737
|
} catch {
|
|
1738
|
+
try {
|
|
1739
|
+
await fs7.access(workspacePath);
|
|
1740
|
+
const repoRoot2 = await this.getRepoRoot();
|
|
1741
|
+
try {
|
|
1742
|
+
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
|
|
1743
|
+
} catch {
|
|
1744
|
+
await fs7.rm(workspacePath, { recursive: true, force: true });
|
|
1745
|
+
}
|
|
1746
|
+
} catch {
|
|
1747
|
+
}
|
|
1716
1748
|
}
|
|
1717
1749
|
const repoRoot = await this.getRepoRoot();
|
|
1718
1750
|
await this.tryFetch(repoRoot);
|
|
@@ -6678,7 +6710,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
6678
6710
|
claimed: new Set(this.state.claimed),
|
|
6679
6711
|
running: new Map(this.state.running),
|
|
6680
6712
|
retryAttempts: new Map(this.state.retryAttempts),
|
|
6681
|
-
completed: new
|
|
6713
|
+
completed: new Map(this.state.completed),
|
|
6682
6714
|
recentRequestTimestamps: [...this.state.recentRequestTimestamps],
|
|
6683
6715
|
recentInputTokens: [...this.state.recentInputTokens],
|
|
6684
6716
|
recentOutputTokens: [...this.state.recentOutputTokens],
|
|
@@ -6786,7 +6818,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
6786
6818
|
running: Array.from(this.state.running.entries()),
|
|
6787
6819
|
retryAttempts: Array.from(this.state.retryAttempts.entries()),
|
|
6788
6820
|
claimed: Array.from(this.state.claimed),
|
|
6789
|
-
completed: Array.from(this.state.completed),
|
|
6821
|
+
completed: Array.from(this.state.completed.keys()),
|
|
6790
6822
|
tokenTotals: { ...this.state.tokenTotals, secondsRunning },
|
|
6791
6823
|
maxConcurrentAgents: this.state.maxConcurrentAgents,
|
|
6792
6824
|
globalCooldownUntilMs: this.state.globalCooldownUntilMs,
|
package/dist/index.mjs
CHANGED
|
@@ -420,7 +420,7 @@ function cloneState(state) {
|
|
|
420
420
|
running: new Map(state.running),
|
|
421
421
|
claimed: new Set(state.claimed),
|
|
422
422
|
retryAttempts: new Map(state.retryAttempts),
|
|
423
|
-
completed: new
|
|
423
|
+
completed: new Map(state.completed),
|
|
424
424
|
tokenTotals: { ...state.tokenTotals },
|
|
425
425
|
rateLimits: { ...state.rateLimits }
|
|
426
426
|
};
|
|
@@ -519,13 +519,39 @@ function resolveBackend(action, hasLocalBackend) {
|
|
|
519
519
|
}
|
|
520
520
|
function pruneCompleted(next) {
|
|
521
521
|
if (next.completed.size <= COMPLETED_PRUNE_THRESHOLD) return;
|
|
522
|
-
for (const id of next.completed) {
|
|
522
|
+
for (const [id] of next.completed) {
|
|
523
523
|
const hasPending = next.retryAttempts.has(id) || next.running.has(id) || next.claimed.has(id);
|
|
524
524
|
if (!hasPending) {
|
|
525
525
|
next.completed.delete(id);
|
|
526
526
|
}
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
|
+
var COMPLETED_GRACE_MULTIPLIER = 2;
|
|
530
|
+
function reconcileCompletedAndClaimed(next, candidates, nowMs, effects) {
|
|
531
|
+
const gracePeriodMs = next.pollIntervalMs * COMPLETED_GRACE_MULTIPLIER;
|
|
532
|
+
const candidateIds = new Set(candidates.map((c) => c.id));
|
|
533
|
+
for (const [id, completedAtMs] of next.completed) {
|
|
534
|
+
if (candidateIds.has(id) && nowMs - completedAtMs > gracePeriodMs) {
|
|
535
|
+
next.completed.delete(id);
|
|
536
|
+
effects.push({
|
|
537
|
+
type: "emitLog",
|
|
538
|
+
level: "info",
|
|
539
|
+
message: `Released completed lock for ${id}: reappeared as active candidate after grace period`
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
for (const id of next.claimed) {
|
|
544
|
+
if (next.running.has(id) || next.retryAttempts.has(id)) continue;
|
|
545
|
+
if (!candidateIds.has(id)) {
|
|
546
|
+
next.claimed.delete(id);
|
|
547
|
+
effects.push({
|
|
548
|
+
type: "emitLog",
|
|
549
|
+
level: "info",
|
|
550
|
+
message: `Released orphaned claim for ${id}: no longer in active candidates`
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
529
555
|
function handleTick(state, event, config) {
|
|
530
556
|
const { candidates, runningStates, nowMs } = event;
|
|
531
557
|
const next = cloneState(state);
|
|
@@ -538,6 +564,7 @@ function handleTick(state, event, config) {
|
|
|
538
564
|
);
|
|
539
565
|
effects.push(...reconcileEffects);
|
|
540
566
|
applyReconcileEffects(next, reconcileEffects);
|
|
567
|
+
reconcileCompletedAndClaimed(next, candidates, nowMs, effects);
|
|
541
568
|
const eligible = selectCandidates(
|
|
542
569
|
candidates,
|
|
543
570
|
next,
|
|
@@ -599,7 +626,7 @@ function handleWorkerExit(state, issueId, reason, error, attempt, config) {
|
|
|
599
626
|
next.running.delete(issueId);
|
|
600
627
|
const nowMs = Date.now();
|
|
601
628
|
if (reason === "normal") {
|
|
602
|
-
next.completed.
|
|
629
|
+
next.completed.set(issueId, nowMs);
|
|
603
630
|
next.claimed.delete(issueId);
|
|
604
631
|
effects.push({
|
|
605
632
|
type: "cleanWorkspace",
|
|
@@ -876,7 +903,7 @@ function createEmptyState(config) {
|
|
|
876
903
|
running: /* @__PURE__ */ new Map(),
|
|
877
904
|
claimed: /* @__PURE__ */ new Set(),
|
|
878
905
|
retryAttempts: /* @__PURE__ */ new Map(),
|
|
879
|
-
completed: /* @__PURE__ */ new
|
|
906
|
+
completed: /* @__PURE__ */ new Map(),
|
|
880
907
|
tokenTotals: {
|
|
881
908
|
inputTokens: 0,
|
|
882
909
|
outputTokens: 0,
|
|
@@ -1342,7 +1369,7 @@ var WorkflowLoader = class {
|
|
|
1342
1369
|
if (parts.length < 3) {
|
|
1343
1370
|
return Err2(
|
|
1344
1371
|
new Error(
|
|
1345
|
-
`Invalid
|
|
1372
|
+
`Invalid harness.orchestrator.md format at ${filePath}. Expected frontmatter surrounded by '---'.`
|
|
1346
1373
|
)
|
|
1347
1374
|
);
|
|
1348
1375
|
}
|
|
@@ -1632,11 +1659,6 @@ var WorkspaceManager = class {
|
|
|
1632
1659
|
const workspacePath = path5.resolve(this.resolvePath(identifier));
|
|
1633
1660
|
try {
|
|
1634
1661
|
await fs7.access(path5.join(workspacePath, ".git"));
|
|
1635
|
-
return Ok6(workspacePath);
|
|
1636
|
-
} catch {
|
|
1637
|
-
}
|
|
1638
|
-
try {
|
|
1639
|
-
await fs7.access(workspacePath);
|
|
1640
1662
|
const repoRoot2 = await this.getRepoRoot();
|
|
1641
1663
|
try {
|
|
1642
1664
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
|
|
@@ -1644,6 +1666,16 @@ var WorkspaceManager = class {
|
|
|
1644
1666
|
await fs7.rm(workspacePath, { recursive: true, force: true });
|
|
1645
1667
|
}
|
|
1646
1668
|
} catch {
|
|
1669
|
+
try {
|
|
1670
|
+
await fs7.access(workspacePath);
|
|
1671
|
+
const repoRoot2 = await this.getRepoRoot();
|
|
1672
|
+
try {
|
|
1673
|
+
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
|
|
1674
|
+
} catch {
|
|
1675
|
+
await fs7.rm(workspacePath, { recursive: true, force: true });
|
|
1676
|
+
}
|
|
1677
|
+
} catch {
|
|
1678
|
+
}
|
|
1647
1679
|
}
|
|
1648
1680
|
const repoRoot = await this.getRepoRoot();
|
|
1649
1681
|
await this.tryFetch(repoRoot);
|
|
@@ -6647,7 +6679,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6647
6679
|
claimed: new Set(this.state.claimed),
|
|
6648
6680
|
running: new Map(this.state.running),
|
|
6649
6681
|
retryAttempts: new Map(this.state.retryAttempts),
|
|
6650
|
-
completed: new
|
|
6682
|
+
completed: new Map(this.state.completed),
|
|
6651
6683
|
recentRequestTimestamps: [...this.state.recentRequestTimestamps],
|
|
6652
6684
|
recentInputTokens: [...this.state.recentInputTokens],
|
|
6653
6685
|
recentOutputTokens: [...this.state.recentOutputTokens],
|
|
@@ -6755,7 +6787,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
6755
6787
|
running: Array.from(this.state.running.entries()),
|
|
6756
6788
|
retryAttempts: Array.from(this.state.retryAttempts.entries()),
|
|
6757
6789
|
claimed: Array.from(this.state.claimed),
|
|
6758
|
-
completed: Array.from(this.state.completed),
|
|
6790
|
+
completed: Array.from(this.state.completed.keys()),
|
|
6759
6791
|
tokenTotals: { ...this.state.tokenTotals, secondsRunning },
|
|
6760
6792
|
maxConcurrentAgents: this.state.maxConcurrentAgents,
|
|
6761
6793
|
globalCooldownUntilMs: this.state.globalCooldownUntilMs,
|