@harness-engineering/orchestrator 0.2.7 → 0.2.8
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/README.md +20 -0
- package/dist/index.d.mts +736 -90
- package/dist/index.d.ts +736 -90
- package/dist/index.js +5747 -484
- package/dist/index.mjs +5752 -485
- package/package.json +8 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { Issue, AgentEvent, TokenUsage, WorkflowConfig, Result, WorkflowDefinition,
|
|
1
|
+
import { Issue, AgentEvent, TokenUsage, ConcernSignal, ScopeTier, EscalationConfig, RoutingDecision, WorkflowConfig, IssueTrackerClient, Result, WorkflowDefinition, TrackerConfig, WorkspaceConfig, HooksConfig, AgentBackend, SessionStartParams, AgentSession, AgentError, TurnParams, TurnResult } from '@harness-engineering/types';
|
|
2
|
+
import { EnrichedSpec, ComplexityScore, SimulationResult } from '@harness-engineering/intelligence';
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
2
4
|
import { EventEmitter } from 'node:events';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Run attempt lifecycle phases (internal to orchestrator).
|
|
6
8
|
* Tracks the current phase of a single run attempt.
|
|
7
9
|
*/
|
|
8
|
-
type RunAttemptPhase = 'PreparingWorkspace' | 'BuildingPrompt' | 'LaunchingAgent' | 'InitializingSession' | 'StreamingTurn' | 'Finishing' | 'Succeeded' | 'Failed' | 'TimedOut' | 'Stalled' | 'CanceledByReconciliation';
|
|
10
|
+
type RunAttemptPhase = 'PreparingWorkspace' | 'BuildingPrompt' | 'LaunchingAgent' | 'InitializingSession' | 'StreamingTurn' | 'RateLimitSleeping' | 'Finishing' | 'Succeeded' | 'Failed' | 'TimedOut' | 'Stalled' | 'CanceledByReconciliation';
|
|
9
11
|
/**
|
|
10
12
|
* Live session metadata tracked while an agent subprocess is running.
|
|
11
13
|
*/
|
|
@@ -55,12 +57,14 @@ interface TokenTotals {
|
|
|
55
57
|
inputTokens: number;
|
|
56
58
|
outputTokens: number;
|
|
57
59
|
totalTokens: number;
|
|
60
|
+
cacheReadTokens: number;
|
|
61
|
+
cacheCreationTokens: number;
|
|
58
62
|
secondsRunning: number;
|
|
59
63
|
}
|
|
60
64
|
/**
|
|
61
65
|
* Rate limit snapshot (populated by agent events).
|
|
62
66
|
*/
|
|
63
|
-
interface RateLimitSnapshot {
|
|
67
|
+
interface RateLimitSnapshot$1 {
|
|
64
68
|
requestsRemaining: number | null;
|
|
65
69
|
requestsLimit: number | null;
|
|
66
70
|
tokensRemaining: number | null;
|
|
@@ -72,25 +76,50 @@ interface RateLimitSnapshot {
|
|
|
72
76
|
interface OrchestratorState {
|
|
73
77
|
pollIntervalMs: number;
|
|
74
78
|
maxConcurrentAgents: number;
|
|
79
|
+
globalCooldownUntilMs: number | null;
|
|
80
|
+
recentRequestTimestamps: number[];
|
|
81
|
+
recentInputTokens: {
|
|
82
|
+
timestamp: number;
|
|
83
|
+
tokens: number;
|
|
84
|
+
}[];
|
|
85
|
+
recentOutputTokens: {
|
|
86
|
+
timestamp: number;
|
|
87
|
+
tokens: number;
|
|
88
|
+
}[];
|
|
89
|
+
globalCooldownMs: number;
|
|
90
|
+
maxRequestsPerMinute: number;
|
|
91
|
+
maxRequestsPerSecond: number;
|
|
92
|
+
maxInputTokensPerMinute: number;
|
|
93
|
+
maxOutputTokensPerMinute: number;
|
|
75
94
|
running: Map<string, RunningEntry>;
|
|
76
95
|
claimed: Set<string>;
|
|
77
96
|
retryAttempts: Map<string, RetryEntry>;
|
|
78
97
|
completed: Set<string>;
|
|
79
98
|
tokenTotals: TokenTotals;
|
|
80
|
-
rateLimits: RateLimitSnapshot;
|
|
99
|
+
rateLimits: RateLimitSnapshot$1;
|
|
100
|
+
/** Running count of claim rejections (another orchestrator won the race). */
|
|
101
|
+
claimRejections: number;
|
|
81
102
|
}
|
|
82
103
|
|
|
83
104
|
/**
|
|
84
105
|
* Discriminated union of events that drive the orchestrator state machine.
|
|
85
106
|
* All events are data -- the caller constructs them from I/O results.
|
|
86
107
|
*/
|
|
87
|
-
type OrchestratorEvent = TickEvent | WorkerExitEvent | AgentUpdateEvent | RetryFiredEvent | StallDetectedEvent;
|
|
108
|
+
type OrchestratorEvent = TickEvent | WorkerExitEvent | AgentUpdateEvent | RetryFiredEvent | StallDetectedEvent | ClaimRejectedEvent;
|
|
88
109
|
interface TickEvent {
|
|
89
110
|
type: 'tick';
|
|
90
111
|
candidates: Issue[];
|
|
91
112
|
runningStates: Map<string, Issue>;
|
|
92
113
|
/** Caller-supplied wall clock (ms since epoch). Keeps state machine pure. */
|
|
93
114
|
nowMs: number;
|
|
115
|
+
/** Pre-computed concern signals from intelligence pipeline (issueId → signals) */
|
|
116
|
+
concernSignals?: Map<string, ConcernSignal[]>;
|
|
117
|
+
/** Pre-computed enriched specs from intelligence pipeline (issueId → spec) */
|
|
118
|
+
enrichedSpecs?: Map<string, EnrichedSpec>;
|
|
119
|
+
/** Pre-computed complexity scores from intelligence pipeline (issueId → score) */
|
|
120
|
+
complexityScores?: Map<string, ComplexityScore>;
|
|
121
|
+
/** Pre-computed PESL simulation results from intelligence pipeline (issueId -> result) */
|
|
122
|
+
simulationResults?: Map<string, SimulationResult>;
|
|
94
123
|
}
|
|
95
124
|
interface WorkerExitEvent {
|
|
96
125
|
type: 'worker_exit';
|
|
@@ -115,15 +144,21 @@ interface StallDetectedEvent {
|
|
|
115
144
|
type: 'stall_detected';
|
|
116
145
|
issueId: string;
|
|
117
146
|
}
|
|
147
|
+
interface ClaimRejectedEvent {
|
|
148
|
+
type: 'claim_rejected';
|
|
149
|
+
issueId: string;
|
|
150
|
+
}
|
|
118
151
|
/**
|
|
119
152
|
* Discriminated union of side effects returned by the state machine.
|
|
120
153
|
* These are data describing what to do -- the orchestrator loop executes them.
|
|
121
154
|
*/
|
|
122
|
-
type SideEffect = DispatchEffect | StopEffect | ScheduleRetryEffect | ReleaseClaimEffect | CleanWorkspaceEffect | UpdateTokensEffect | EmitLogEffect;
|
|
155
|
+
type SideEffect = DispatchEffect | StopEffect | ScheduleRetryEffect | ReleaseClaimEffect | CleanWorkspaceEffect | UpdateTokensEffect | EmitLogEffect | EscalateEffect | ClaimEffect;
|
|
123
156
|
interface DispatchEffect {
|
|
124
157
|
type: 'dispatch';
|
|
125
158
|
issue: Issue;
|
|
126
159
|
attempt: number | null;
|
|
160
|
+
/** Which backend to dispatch to. Defaults to 'primary' for backward compat. */
|
|
161
|
+
backend?: 'local' | 'primary';
|
|
127
162
|
}
|
|
128
163
|
interface StopEffect {
|
|
129
164
|
type: 'stop';
|
|
@@ -158,6 +193,28 @@ interface EmitLogEffect {
|
|
|
158
193
|
message: string;
|
|
159
194
|
context?: Record<string, unknown>;
|
|
160
195
|
}
|
|
196
|
+
interface EscalateEffect {
|
|
197
|
+
type: 'escalate';
|
|
198
|
+
issueId: string;
|
|
199
|
+
identifier: string;
|
|
200
|
+
reasons: string[];
|
|
201
|
+
/** Issue title for context in the interaction queue */
|
|
202
|
+
issueTitle?: string;
|
|
203
|
+
/** Issue description for context in the interaction queue */
|
|
204
|
+
issueDescription?: string | null;
|
|
205
|
+
/** Enriched spec from intelligence pipeline, if available */
|
|
206
|
+
enrichedSpec?: EnrichedSpec;
|
|
207
|
+
/** Complexity score from intelligence pipeline, if available */
|
|
208
|
+
complexityScore?: ComplexityScore;
|
|
209
|
+
}
|
|
210
|
+
interface ClaimEffect {
|
|
211
|
+
type: 'claim';
|
|
212
|
+
issue: Issue;
|
|
213
|
+
/** Which backend to dispatch to after a successful claim */
|
|
214
|
+
backend?: 'local' | 'primary';
|
|
215
|
+
/** Retry attempt number, if this is a retry dispatch */
|
|
216
|
+
attempt: number | null;
|
|
217
|
+
}
|
|
161
218
|
|
|
162
219
|
/**
|
|
163
220
|
* Calculate retry delay based on attempt number and retry type.
|
|
@@ -211,10 +268,245 @@ declare function canDispatch(state: OrchestratorState, issueState: string, maxCo
|
|
|
211
268
|
*/
|
|
212
269
|
declare function reconcile(state: OrchestratorState, runningStates: ReadonlyMap<string, Issue>, activeStates: string[], terminalStates: string[]): SideEffect[];
|
|
213
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Artifact presence metadata for scope tier detection.
|
|
273
|
+
*/
|
|
274
|
+
interface ArtifactPresence {
|
|
275
|
+
hasSpec: boolean;
|
|
276
|
+
hasPlans: boolean;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Derive artifact presence from an Issue's spec and plans fields.
|
|
280
|
+
*/
|
|
281
|
+
declare function artifactPresenceFromIssue(issue: Issue): ArtifactPresence;
|
|
282
|
+
/**
|
|
283
|
+
* Detect the scope tier for an issue based on label overrides and artifact presence.
|
|
284
|
+
*
|
|
285
|
+
* Label override (e.g., `scope:quick-fix`) takes precedence.
|
|
286
|
+
* Otherwise, infer from spec/plan presence:
|
|
287
|
+
* - No spec, no plan -> full-exploration
|
|
288
|
+
* - Spec or plan exists -> guided-change
|
|
289
|
+
*/
|
|
290
|
+
declare function detectScopeTier(issue: Issue, artifacts: ArtifactPresence): ScopeTier;
|
|
291
|
+
/**
|
|
292
|
+
* Pure routing function. Determines whether an issue should be dispatched
|
|
293
|
+
* to the local backend, the primary backend, or escalated to needs-human.
|
|
294
|
+
*
|
|
295
|
+
* Routing rules (in order):
|
|
296
|
+
* 1. If tier is in alwaysHuman -> needs-human
|
|
297
|
+
* 2. If tier is in primaryExecute -> dispatch-primary
|
|
298
|
+
* 3. If tier is in autoExecute -> dispatch-local
|
|
299
|
+
* 4. If tier is in signalGated -> check concern signals
|
|
300
|
+
* 5. Otherwise -> dispatch-local (safe default)
|
|
301
|
+
*/
|
|
302
|
+
declare function routeIssue(scopeTier: ScopeTier, concernSignals: ConcernSignal[], config: EscalationConfig): RoutingDecision;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Candidate skills the orchestrator may dispatch to. Keep this set
|
|
306
|
+
* small on purpose — the triage router is a coarse routing layer, not
|
|
307
|
+
* a skill registry.
|
|
308
|
+
*/
|
|
309
|
+
type TriageSkill = 'code-review' | 'security-review' | 'planning' | 'debugging' | 'refactoring' | 'docs';
|
|
310
|
+
/**
|
|
311
|
+
* Shape-of-work signals the triage router consumes. These are derived
|
|
312
|
+
* from the Issue plus diff metadata by the caller.
|
|
313
|
+
*/
|
|
314
|
+
interface TriageSignals {
|
|
315
|
+
/** Raw title/body prefix, e.g. "feat:", "fix:", "docs:", "security:" */
|
|
316
|
+
titlePrefix?: string;
|
|
317
|
+
/** True if the diff touches auth, crypto, session, or similar paths */
|
|
318
|
+
touchesSecuritySensitivePaths?: boolean;
|
|
319
|
+
/** True if the diff touches a migrations or schema directory */
|
|
320
|
+
touchesMigrationPaths?: boolean;
|
|
321
|
+
/** Count of changed files in the diff */
|
|
322
|
+
changedFileCount?: number;
|
|
323
|
+
/** Whether tests are failing on the issue branch */
|
|
324
|
+
hasFailingTests?: boolean;
|
|
325
|
+
/** Whether the issue is marked or labeled as a rollback */
|
|
326
|
+
isRollback?: boolean;
|
|
327
|
+
/** True if every changed file is `.md` */
|
|
328
|
+
isDocsOnly?: boolean;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Structured decision the orchestrator persists and consumes.
|
|
332
|
+
*/
|
|
333
|
+
interface TriageDecision {
|
|
334
|
+
/** Target skill for dispatch. */
|
|
335
|
+
skill: TriageSkill;
|
|
336
|
+
/** Specific agent within the skill, optional. */
|
|
337
|
+
agent?: string;
|
|
338
|
+
/** Router confidence in the decision. */
|
|
339
|
+
confidence: 'high' | 'medium' | 'low';
|
|
340
|
+
/** Human-readable justifications (one entry per rule that matched). */
|
|
341
|
+
reasons: string[];
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Optional user/project overrides for path heuristics and rule thresholds.
|
|
345
|
+
*/
|
|
346
|
+
interface TriageConfig {
|
|
347
|
+
/** Max changed files for a fix to still route to code-review (default 3). */
|
|
348
|
+
smallFixChangedFileMax?: number;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Extract the conventional-commit prefix (lowercased) from a title.
|
|
352
|
+
* Returns undefined if no prefix is present.
|
|
353
|
+
*/
|
|
354
|
+
declare function extractTitlePrefix(title: string | null | undefined): string | undefined;
|
|
355
|
+
/**
|
|
356
|
+
* Decide which skill/agent the orchestrator should dispatch this issue to.
|
|
357
|
+
*
|
|
358
|
+
* Rule order (first match wins):
|
|
359
|
+
* 1. isRollback → debugging (high)
|
|
360
|
+
* 2. security prefix or security paths → security-review (high)
|
|
361
|
+
* 3. docs prefix or docs-only diff → docs (high)
|
|
362
|
+
* 4. hasFailingTests → debugging (medium)
|
|
363
|
+
* 5. touchesMigrationPaths → planning (high)
|
|
364
|
+
* 6. fix: with small change → code-review (high)
|
|
365
|
+
* 7. feat: → planning (medium)
|
|
366
|
+
* 8. refactor: → refactoring (medium)
|
|
367
|
+
* 9. default → code-review (low)
|
|
368
|
+
*
|
|
369
|
+
* Triage is meant to run BEFORE `routeIssue()`; the selected skill is
|
|
370
|
+
* persisted on the live session so downstream dispatch does not
|
|
371
|
+
* re-derive it.
|
|
372
|
+
*/
|
|
373
|
+
declare function triageIssue(issue: Issue, signals: TriageSignals, config?: TriageConfig): TriageDecision;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* A pending human interaction, typically from an escalation.
|
|
377
|
+
*/
|
|
378
|
+
interface PendingInteraction {
|
|
379
|
+
/** Unique interaction ID */
|
|
380
|
+
id: string;
|
|
381
|
+
/** ID of the related issue */
|
|
382
|
+
issueId: string;
|
|
383
|
+
/** Interaction type */
|
|
384
|
+
type: 'needs-human';
|
|
385
|
+
/** Reasons for escalation */
|
|
386
|
+
reasons: string[];
|
|
387
|
+
/** Context for the human */
|
|
388
|
+
context: {
|
|
389
|
+
issueTitle: string;
|
|
390
|
+
issueDescription: string | null;
|
|
391
|
+
specPath: string | null;
|
|
392
|
+
planPath: string | null;
|
|
393
|
+
relatedFiles: string[];
|
|
394
|
+
/** Enriched spec from intelligence pipeline, if available */
|
|
395
|
+
enrichedSpec?: {
|
|
396
|
+
intent: string;
|
|
397
|
+
summary: string;
|
|
398
|
+
affectedSystems: unknown[];
|
|
399
|
+
unknowns: string[];
|
|
400
|
+
ambiguities: string[];
|
|
401
|
+
riskSignals: string[];
|
|
402
|
+
};
|
|
403
|
+
/** Complexity score from intelligence pipeline, if available */
|
|
404
|
+
complexityScore?: {
|
|
405
|
+
overall: number;
|
|
406
|
+
confidence: number;
|
|
407
|
+
riskLevel: string;
|
|
408
|
+
blastRadius: {
|
|
409
|
+
services: number;
|
|
410
|
+
modules: number;
|
|
411
|
+
filesEstimated: number;
|
|
412
|
+
testFilesAffected: number;
|
|
413
|
+
};
|
|
414
|
+
dimensions: {
|
|
415
|
+
structural: number;
|
|
416
|
+
semantic: number;
|
|
417
|
+
historical: number;
|
|
418
|
+
};
|
|
419
|
+
reasoning: string[];
|
|
420
|
+
recommendedRoute: string;
|
|
421
|
+
};
|
|
422
|
+
};
|
|
423
|
+
/** ISO timestamp of creation */
|
|
424
|
+
createdAt: string;
|
|
425
|
+
/** Current status */
|
|
426
|
+
status: 'pending' | 'claimed' | 'resolved';
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Persistent queue of pending human interactions.
|
|
430
|
+
* Each interaction is stored as a separate JSON file in the configured directory.
|
|
431
|
+
*/
|
|
432
|
+
declare class InteractionQueue {
|
|
433
|
+
private dir;
|
|
434
|
+
private pushListeners;
|
|
435
|
+
/**
|
|
436
|
+
* @param dir - Directory path for storing interaction JSON files
|
|
437
|
+
*/
|
|
438
|
+
constructor(dir: string);
|
|
439
|
+
/**
|
|
440
|
+
* Register a listener that fires after each push.
|
|
441
|
+
*/
|
|
442
|
+
onPush(listener: (interaction: PendingInteraction) => void): void;
|
|
443
|
+
/**
|
|
444
|
+
* Push a new interaction to the queue.
|
|
445
|
+
* If a pending interaction for the same issueId already exists, it is
|
|
446
|
+
* replaced (upsert) so the directory never accumulates duplicates.
|
|
447
|
+
*/
|
|
448
|
+
push(interaction: PendingInteraction): Promise<void>;
|
|
449
|
+
/**
|
|
450
|
+
* List all interactions (regardless of status).
|
|
451
|
+
*/
|
|
452
|
+
list(): Promise<PendingInteraction[]>;
|
|
453
|
+
/**
|
|
454
|
+
* List only pending interactions.
|
|
455
|
+
*/
|
|
456
|
+
listPending(): Promise<PendingInteraction[]>;
|
|
457
|
+
/**
|
|
458
|
+
* Update the status of an interaction.
|
|
459
|
+
*/
|
|
460
|
+
updateStatus(id: string, status: PendingInteraction['status']): Promise<void>;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* A persisted record of a single intelligence pipeline analysis run.
|
|
465
|
+
*/
|
|
466
|
+
interface AnalysisRecord {
|
|
467
|
+
/** Issue ID this analysis belongs to */
|
|
468
|
+
issueId: string;
|
|
469
|
+
/** Issue identifier (human-readable) */
|
|
470
|
+
identifier: string;
|
|
471
|
+
/** Enriched spec from SEL, or null if skipped */
|
|
472
|
+
spec: EnrichedSpec | null;
|
|
473
|
+
/** Complexity score from CML, or null if skipped */
|
|
474
|
+
score: ComplexityScore | null;
|
|
475
|
+
/** PESL simulation result, or null if not run */
|
|
476
|
+
simulation: SimulationResult | null;
|
|
477
|
+
/** ISO timestamp when this analysis was recorded */
|
|
478
|
+
analyzedAt: string;
|
|
479
|
+
/** External tracker ID (e.g., "github:owner/repo#42"), populated at analysis time. Null if no tracker configured. */
|
|
480
|
+
externalId: string | null;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Persistent archive of intelligence pipeline analysis results.
|
|
484
|
+
* Each analysis is stored as a separate JSON file keyed by issue ID.
|
|
485
|
+
* Overwrites previous analysis for the same issue (latest result wins).
|
|
486
|
+
*/
|
|
487
|
+
declare class AnalysisArchive {
|
|
488
|
+
private dir;
|
|
489
|
+
constructor(dir: string);
|
|
490
|
+
private safePath;
|
|
491
|
+
/**
|
|
492
|
+
* Write an analysis record to disk, overwriting any previous record for the same issue.
|
|
493
|
+
*/
|
|
494
|
+
save(record: AnalysisRecord): Promise<void>;
|
|
495
|
+
/**
|
|
496
|
+
* Read the analysis record for a specific issue.
|
|
497
|
+
*/
|
|
498
|
+
get(issueId: string): Promise<AnalysisRecord | null>;
|
|
499
|
+
/**
|
|
500
|
+
* List all archived analysis records.
|
|
501
|
+
*/
|
|
502
|
+
list(): Promise<AnalysisRecord[]>;
|
|
503
|
+
}
|
|
504
|
+
|
|
214
505
|
interface ApplyEventResult {
|
|
215
506
|
nextState: OrchestratorState;
|
|
216
507
|
effects: SideEffect[];
|
|
217
508
|
}
|
|
509
|
+
declare function resolveEscalationConfig(config: WorkflowConfig): EscalationConfig;
|
|
218
510
|
/**
|
|
219
511
|
* Pure state machine transition function.
|
|
220
512
|
*
|
|
@@ -229,6 +521,189 @@ declare function applyEvent(state: OrchestratorState, event: OrchestratorEvent,
|
|
|
229
521
|
*/
|
|
230
522
|
declare function createEmptyState(config: WorkflowConfig): OrchestratorState;
|
|
231
523
|
|
|
524
|
+
/**
|
|
525
|
+
* Computes the delay (in ms) needed before the next API request can be made,
|
|
526
|
+
* based on a snapshot of recent request/token activity and rate-limit config.
|
|
527
|
+
*
|
|
528
|
+
* Returns 0 when no throttling is needed.
|
|
529
|
+
*/
|
|
530
|
+
interface RateLimitSnapshot {
|
|
531
|
+
globalCooldownUntilMs: number | null;
|
|
532
|
+
recentRequestTimestamps: number[];
|
|
533
|
+
recentInputTokens: {
|
|
534
|
+
timestamp: number;
|
|
535
|
+
tokens: number;
|
|
536
|
+
}[];
|
|
537
|
+
recentOutputTokens: {
|
|
538
|
+
timestamp: number;
|
|
539
|
+
tokens: number;
|
|
540
|
+
}[];
|
|
541
|
+
}
|
|
542
|
+
interface RateLimitConfig {
|
|
543
|
+
maxRequestsPerMinute: number;
|
|
544
|
+
maxRequestsPerSecond: number;
|
|
545
|
+
maxInputTokensPerMinute: number;
|
|
546
|
+
maxOutputTokensPerMinute: number;
|
|
547
|
+
}
|
|
548
|
+
declare function computeRateLimitDelay(snapshot: RateLimitSnapshot, config: RateLimitConfig): number;
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Renders an AnalysisRecord as a structured markdown comment.
|
|
552
|
+
* Format: summary header + reasoning bullets + collapsible JSON with discriminator.
|
|
553
|
+
*
|
|
554
|
+
* Used by both the orchestrator auto-publish and the CLI publish-analyses command.
|
|
555
|
+
*/
|
|
556
|
+
declare function renderAnalysisComment(record: AnalysisRecord): string;
|
|
557
|
+
|
|
558
|
+
interface PublishedIndex {
|
|
559
|
+
[issueId: string]: string;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Load the published analyses index from disk.
|
|
563
|
+
* Returns an empty object if the file does not exist or is unparseable.
|
|
564
|
+
*/
|
|
565
|
+
declare function loadPublishedIndex(projectRoot: string): PublishedIndex;
|
|
566
|
+
/**
|
|
567
|
+
* Persist the published analyses index to disk.
|
|
568
|
+
* Creates parent directories if they do not exist.
|
|
569
|
+
*/
|
|
570
|
+
declare function savePublishedIndex(projectRoot: string, index: PublishedIndex): void;
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Resolves the orchestrator identity. Uses the explicit configId if
|
|
574
|
+
* provided; otherwise reads or creates a persisted machine UUID at
|
|
575
|
+
* ~/.harness/orchestrator-id and derives a stable hash.
|
|
576
|
+
*
|
|
577
|
+
* Format: `orchestrator-{first8charsOfSha256(uuid)}`
|
|
578
|
+
* Example: `orchestrator-a7f3b2c1`
|
|
579
|
+
*/
|
|
580
|
+
declare function resolveOrchestratorId(configId?: string): Promise<string>;
|
|
581
|
+
/** Exposed for testing only -- returns the path to the identity file. */
|
|
582
|
+
declare const ORCHESTRATOR_IDENTITY_FILE: string;
|
|
583
|
+
|
|
584
|
+
interface ClaimManagerConfig {
|
|
585
|
+
/** Delay in ms between claim write and verification read. Default: 2000 */
|
|
586
|
+
verifyDelayMs?: number;
|
|
587
|
+
/** State name used for claimed/in-progress issues. Default: 'in-progress' */
|
|
588
|
+
claimedState?: string;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Manages claim coordination for multi-orchestrator dispatch.
|
|
592
|
+
*
|
|
593
|
+
* Uses the tracker as the shared state layer. Claims are optimistic:
|
|
594
|
+
* write then verify after a delay to detect races.
|
|
595
|
+
*/
|
|
596
|
+
declare class ClaimManager {
|
|
597
|
+
private tracker;
|
|
598
|
+
private orchestratorId;
|
|
599
|
+
private verifyDelayMs;
|
|
600
|
+
private claimedState;
|
|
601
|
+
constructor(tracker: IssueTrackerClient, orchestratorId: string, config?: ClaimManagerConfig);
|
|
602
|
+
/**
|
|
603
|
+
* Optimistically claims an issue then verifies ownership after a delay.
|
|
604
|
+
*
|
|
605
|
+
* Returns 'claimed' if the assignee matches this orchestrator after
|
|
606
|
+
* the verify delay, 'rejected' if another orchestrator won the race.
|
|
607
|
+
*/
|
|
608
|
+
claimAndVerify(issueId: string): Promise<Result<'claimed' | 'rejected', Error>>;
|
|
609
|
+
/**
|
|
610
|
+
* Releases a claimed issue back to the available pool.
|
|
611
|
+
*/
|
|
612
|
+
release(issueId: string): Promise<Result<void, Error>>;
|
|
613
|
+
/**
|
|
614
|
+
* Refreshes claim timestamps for all running issues in parallel.
|
|
615
|
+
* Failures are non-fatal — individual heartbeat failures are swallowed
|
|
616
|
+
* so one failing claim does not block others.
|
|
617
|
+
*/
|
|
618
|
+
heartbeat(issueIds: string[]): Promise<void>;
|
|
619
|
+
/**
|
|
620
|
+
* Checks whether an issue's claim is stale based on its updatedAt
|
|
621
|
+
* timestamp and the configured TTL.
|
|
622
|
+
*
|
|
623
|
+
* Returns true if the claim should be considered expired (the
|
|
624
|
+
* owning orchestrator may have crashed).
|
|
625
|
+
*/
|
|
626
|
+
isStale(issue: Issue, ttlMs: number): boolean;
|
|
627
|
+
/**
|
|
628
|
+
* Scans the tracker for "in-progress" issues assigned to this orchestrator
|
|
629
|
+
* and releases any that are not currently running in memory.
|
|
630
|
+
*
|
|
631
|
+
* Called once during orchestrator startup to clean up orphaned claims
|
|
632
|
+
* from a previous crash or unclean shutdown.
|
|
633
|
+
*
|
|
634
|
+
* @param runningIssueIds - Set of issue IDs currently in the running map
|
|
635
|
+
* @returns List of issue IDs that were released
|
|
636
|
+
*/
|
|
637
|
+
reconcileOnStartup(runningIssueIds: ReadonlySet<string>): Promise<Result<string[], Error>>;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Minimal logger interface for PR detection.
|
|
642
|
+
* Accepts any structured logger that provides debug/info/warn.
|
|
643
|
+
*/
|
|
644
|
+
interface PRDetectorLogger {
|
|
645
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
646
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
647
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Function signature compatible with Node's `child_process.execFile`.
|
|
651
|
+
* Allows injection for testing.
|
|
652
|
+
*/
|
|
653
|
+
type ExecFileFn = typeof execFile;
|
|
654
|
+
/**
|
|
655
|
+
* Detects whether GitHub issues or branches already have open pull requests.
|
|
656
|
+
*
|
|
657
|
+
* Uses the `gh` CLI under the hood. All checks are fail-open: if `gh` is not
|
|
658
|
+
* installed, auth is missing, or the network is down, candidates pass through
|
|
659
|
+
* rather than being incorrectly blocked.
|
|
660
|
+
*/
|
|
661
|
+
declare class PRDetector {
|
|
662
|
+
private logger;
|
|
663
|
+
private execFileFn;
|
|
664
|
+
private projectRoot;
|
|
665
|
+
constructor(opts: {
|
|
666
|
+
logger: PRDetectorLogger;
|
|
667
|
+
execFileFn?: ExecFileFn;
|
|
668
|
+
projectRoot: string;
|
|
669
|
+
});
|
|
670
|
+
/**
|
|
671
|
+
* Parse a `github:owner/repo#N` externalId into its parts.
|
|
672
|
+
* Returns null for invalid or non-GitHub formats.
|
|
673
|
+
*/
|
|
674
|
+
parseExternalId(externalId: string): {
|
|
675
|
+
owner: string;
|
|
676
|
+
repo: string;
|
|
677
|
+
number: number;
|
|
678
|
+
} | null;
|
|
679
|
+
/**
|
|
680
|
+
* Checks whether a remote branch has an open pull request via `gh`.
|
|
681
|
+
* Returns true if a PR exists, false otherwise. Failures are treated
|
|
682
|
+
* as "no PR" to err on the side of preserving work.
|
|
683
|
+
*/
|
|
684
|
+
branchHasPullRequest(branch: string): Promise<boolean>;
|
|
685
|
+
/**
|
|
686
|
+
* Checks whether a GitHub issue (identified by externalId) has an open PR
|
|
687
|
+
* linked to it via `closes #N` or similar keywords. Fail-open on API errors
|
|
688
|
+
* or non-GitHub externalId formats.
|
|
689
|
+
*/
|
|
690
|
+
hasOpenPRForExternalId(externalId: string): Promise<boolean>;
|
|
691
|
+
/**
|
|
692
|
+
* Checks whether an issue identifier has an open GitHub PR by searching
|
|
693
|
+
* for a branch matching the `feat/<identifier>` naming convention used
|
|
694
|
+
* by dispatched agents. Fail-open on API errors.
|
|
695
|
+
*/
|
|
696
|
+
hasOpenPRForIdentifier(identifier: string): Promise<boolean>;
|
|
697
|
+
/**
|
|
698
|
+
* Filters out candidates that already have an open GitHub PR, running
|
|
699
|
+
* checks with limited concurrency to avoid overwhelming the GitHub API.
|
|
700
|
+
* For candidates with an externalId, searches for PRs linked to the
|
|
701
|
+
* GitHub issue. Falls back to `feat/<identifier>` branch lookup otherwise.
|
|
702
|
+
* Fail-open on API errors.
|
|
703
|
+
*/
|
|
704
|
+
filterCandidatesWithOpenPRs(candidates: Issue[]): Promise<Issue[]>;
|
|
705
|
+
}
|
|
706
|
+
|
|
232
707
|
declare class WorkflowLoader {
|
|
233
708
|
loadWorkflow(filePath: string): Promise<Result<WorkflowDefinition, Error>>;
|
|
234
709
|
}
|
|
@@ -261,6 +736,28 @@ declare class RoadmapTrackerAdapter implements IssueTrackerClient {
|
|
|
261
736
|
* @param stateNames - List of statuses to filter by
|
|
262
737
|
*/
|
|
263
738
|
fetchIssuesByStates(stateNames: string[]): Promise<Result<Issue[], Error>>;
|
|
739
|
+
/**
|
|
740
|
+
* Transitions a roadmap feature into the first configured terminal state
|
|
741
|
+
* and rewrites the markdown file. Idempotent: if the feature is already
|
|
742
|
+
* in a terminal state, this is a no-op.
|
|
743
|
+
*
|
|
744
|
+
* The orchestrator calls this after a successful agent exit so the feature
|
|
745
|
+
* is no longer returned by `fetchCandidateIssues` on the next tick — and,
|
|
746
|
+
* critically, after an orchestrator restart.
|
|
747
|
+
*/
|
|
748
|
+
markIssueComplete(issueId: string): Promise<Result<void, Error>>;
|
|
749
|
+
/**
|
|
750
|
+
* Claims an issue by transitioning its status to "in-progress" and
|
|
751
|
+
* writing the orchestratorId into the assignee field. Idempotent if
|
|
752
|
+
* already claimed by the same orchestratorId.
|
|
753
|
+
*/
|
|
754
|
+
claimIssue(issueId: string, orchestratorId: string): Promise<Result<void, Error>>;
|
|
755
|
+
/**
|
|
756
|
+
* Releases a claimed issue by transitioning back to the first active
|
|
757
|
+
* state and clearing the assignee field.
|
|
758
|
+
*/
|
|
759
|
+
releaseIssue(issueId: string): Promise<Result<void, Error>>;
|
|
760
|
+
private findFeatureById;
|
|
264
761
|
/**
|
|
265
762
|
* Fetches full issue details for a list of identifiers.
|
|
266
763
|
*
|
|
@@ -290,7 +787,11 @@ declare class LinearGraphQLStub implements LinearGraphQLExtension {
|
|
|
290
787
|
|
|
291
788
|
declare class WorkspaceManager {
|
|
292
789
|
private config;
|
|
790
|
+
/** Absolute path to the git repository root (resolved lazily). */
|
|
791
|
+
private repoRoot;
|
|
293
792
|
constructor(config: WorkspaceConfig);
|
|
793
|
+
/** Runs a git command and returns stdout. Extracted for testability. */
|
|
794
|
+
protected git(args: string[], cwd: string): Promise<string>;
|
|
294
795
|
/**
|
|
295
796
|
* Sanitizes an issue identifier to be safe for use as a directory name.
|
|
296
797
|
*/
|
|
@@ -300,15 +801,45 @@ declare class WorkspaceManager {
|
|
|
300
801
|
*/
|
|
301
802
|
resolvePath(identifier: string): string;
|
|
302
803
|
/**
|
|
303
|
-
*
|
|
804
|
+
* Discovers the git repository root from the workspace root directory.
|
|
805
|
+
*/
|
|
806
|
+
private getRepoRoot;
|
|
807
|
+
/**
|
|
808
|
+
* Ensures the workspace exists as a git worktree so the agent has
|
|
809
|
+
* access to the full project source.
|
|
304
810
|
*/
|
|
305
811
|
ensureWorkspace(identifier: string): Promise<Result<string, Error>>;
|
|
812
|
+
/**
|
|
813
|
+
* Best-effort `git fetch origin` so subsequent ref resolution sees the
|
|
814
|
+
* latest remote state. Failures (offline, no remote, auth errors) are
|
|
815
|
+
* swallowed — dispatch should not be blocked by transient network issues.
|
|
816
|
+
*/
|
|
817
|
+
private tryFetch;
|
|
818
|
+
/**
|
|
819
|
+
* Resolves the ref that new worktrees should be based on.
|
|
820
|
+
*
|
|
821
|
+
* Priority order:
|
|
822
|
+
* 1. `config.baseRef` (explicit override). Throws if it doesn't resolve.
|
|
823
|
+
* 2. Default branch via `git symbolic-ref --short refs/remotes/origin/HEAD`.
|
|
824
|
+
* 3. Common fallbacks: `origin/main`, `origin/master`, `main`, `master`.
|
|
825
|
+
* 4. `HEAD` as an ultimate fallback (preserves old behavior for unusual
|
|
826
|
+
* repos without any of the above).
|
|
827
|
+
*/
|
|
828
|
+
private resolveBaseRef;
|
|
829
|
+
/** Returns true iff `git rev-parse --verify` accepts the ref. */
|
|
830
|
+
private refExists;
|
|
306
831
|
/**
|
|
307
832
|
* Checks if a workspace exists.
|
|
308
833
|
*/
|
|
309
834
|
exists(identifier: string): Promise<boolean>;
|
|
310
835
|
/**
|
|
311
|
-
*
|
|
836
|
+
* Checks whether a worktree has commits ahead of the base branch that have
|
|
837
|
+
* been pushed to a remote branch. Returns the remote branch name if found,
|
|
838
|
+
* or null if the worktree is on a detached HEAD with no pushed branch.
|
|
839
|
+
*/
|
|
840
|
+
findPushedBranch(identifier: string): Promise<string | null>;
|
|
841
|
+
/**
|
|
842
|
+
* Removes a workspace directory and its git worktree registration.
|
|
312
843
|
*/
|
|
313
844
|
removeWorkspace(identifier: string): Promise<Result<void, Error>>;
|
|
314
845
|
}
|
|
@@ -334,86 +865,65 @@ declare class MockBackend implements AgentBackend {
|
|
|
334
865
|
healthCheck(): Promise<Result<void, AgentError>>;
|
|
335
866
|
}
|
|
336
867
|
|
|
337
|
-
declare class ClaudeBackend implements AgentBackend {
|
|
338
|
-
readonly name = "claude";
|
|
339
|
-
private command;
|
|
340
|
-
constructor(command?: string);
|
|
341
|
-
startSession(params: SessionStartParams): Promise<Result<AgentSession, AgentError>>;
|
|
342
|
-
runTurn(session: AgentSession, params: TurnParams): AsyncGenerator<AgentEvent, TurnResult, void>;
|
|
343
|
-
stopSession(_session: AgentSession): Promise<Result<void, AgentError>>;
|
|
344
|
-
healthCheck(): Promise<Result<void, AgentError>>;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
interface OpenAIBackendConfig {
|
|
348
|
-
/** OpenAI model to use. Defaults to 'gpt-4o'. */
|
|
349
|
-
model?: string;
|
|
350
|
-
/** API key. Defaults to process.env.OPENAI_API_KEY. */
|
|
351
|
-
apiKey?: string;
|
|
352
|
-
}
|
|
353
|
-
interface OpenAISession extends AgentSession {
|
|
354
|
-
systemPrompt?: string;
|
|
355
|
-
}
|
|
356
|
-
declare class OpenAIBackend implements AgentBackend {
|
|
357
|
-
readonly name = "openai";
|
|
358
|
-
private config;
|
|
359
|
-
private client;
|
|
360
|
-
private cacheAdapter;
|
|
361
|
-
constructor(config?: OpenAIBackendConfig);
|
|
362
|
-
startSession(params: SessionStartParams): Promise<Result<AgentSession, AgentError>>;
|
|
363
|
-
runTurn(session: AgentSession, params: TurnParams): AsyncGenerator<AgentEvent, TurnResult, void>;
|
|
364
|
-
stopSession(_session: AgentSession): Promise<Result<void, AgentError>>;
|
|
365
|
-
healthCheck(): Promise<Result<void, AgentError>>;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
interface GeminiBackendConfig {
|
|
369
|
-
/** Gemini model to use. Defaults to 'gemini-2.0-flash'. */
|
|
370
|
-
model?: string;
|
|
371
|
-
/** API key. Defaults to process.env.GEMINI_API_KEY or process.env.GOOGLE_API_KEY. */
|
|
372
|
-
apiKey?: string;
|
|
373
|
-
}
|
|
374
|
-
interface GeminiSession extends AgentSession {
|
|
375
|
-
systemPrompt?: string;
|
|
376
|
-
}
|
|
377
|
-
declare class GeminiBackend implements AgentBackend {
|
|
378
|
-
readonly name = "gemini";
|
|
379
|
-
private config;
|
|
380
|
-
private cacheAdapter;
|
|
381
|
-
constructor(config?: GeminiBackendConfig);
|
|
382
|
-
startSession(params: SessionStartParams): Promise<Result<AgentSession, AgentError>>;
|
|
383
|
-
runTurn(session: AgentSession, params: TurnParams): AsyncGenerator<AgentEvent, TurnResult, void>;
|
|
384
|
-
stopSession(_session: AgentSession): Promise<Result<void, AgentError>>;
|
|
385
|
-
healthCheck(): Promise<Result<void, AgentError>>;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
interface AnthropicBackendConfig {
|
|
389
|
-
/** Anthropic model to use. Defaults to 'claude-sonnet-4-20250514'. */
|
|
390
|
-
model?: string;
|
|
391
|
-
/** API key. Defaults to process.env.ANTHROPIC_API_KEY. */
|
|
392
|
-
apiKey?: string;
|
|
393
|
-
/** Maximum output tokens. Defaults to 4096. */
|
|
394
|
-
maxTokens?: number;
|
|
395
|
-
}
|
|
396
|
-
interface AnthropicSession extends AgentSession {
|
|
397
|
-
systemPrompt?: string;
|
|
398
|
-
}
|
|
399
|
-
declare class AnthropicBackend implements AgentBackend {
|
|
400
|
-
readonly name = "anthropic";
|
|
401
|
-
private config;
|
|
402
|
-
private client;
|
|
403
|
-
private cacheAdapter;
|
|
404
|
-
constructor(config?: AnthropicBackendConfig);
|
|
405
|
-
startSession(params: SessionStartParams): Promise<Result<AgentSession, AgentError>>;
|
|
406
|
-
runTurn(session: AgentSession, params: TurnParams): AsyncGenerator<AgentEvent, TurnResult, void>;
|
|
407
|
-
stopSession(_session: AgentSession): Promise<Result<void, AgentError>>;
|
|
408
|
-
healthCheck(): Promise<Result<void, AgentError>>;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
868
|
declare class PromptRenderer {
|
|
412
869
|
private engine;
|
|
413
870
|
constructor();
|
|
414
871
|
render(template: string, context: Record<string, unknown>): Promise<string>;
|
|
415
872
|
}
|
|
416
873
|
|
|
874
|
+
/**
|
|
875
|
+
* Result of a single maintenance task run.
|
|
876
|
+
*/
|
|
877
|
+
interface RunResult {
|
|
878
|
+
/** ID of the task that was run */
|
|
879
|
+
taskId: string;
|
|
880
|
+
/** ISO timestamp when the run started */
|
|
881
|
+
startedAt: string;
|
|
882
|
+
/** ISO timestamp when the run completed */
|
|
883
|
+
completedAt: string;
|
|
884
|
+
/** Outcome of the run */
|
|
885
|
+
status: 'success' | 'failure' | 'skipped' | 'no-issues';
|
|
886
|
+
/** Number of issues/findings detected */
|
|
887
|
+
findings: number;
|
|
888
|
+
/** Number of issues fixed */
|
|
889
|
+
fixed: number;
|
|
890
|
+
/** URL of the created/updated PR, or null if no PR was created */
|
|
891
|
+
prUrl: string | null;
|
|
892
|
+
/** Whether an existing PR was updated (vs newly created) */
|
|
893
|
+
prUpdated: boolean;
|
|
894
|
+
/** Error message if status is 'failure' */
|
|
895
|
+
error?: string;
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Schedule entry for a single task, used in MaintenanceStatus.
|
|
899
|
+
*/
|
|
900
|
+
interface ScheduleEntry {
|
|
901
|
+
/** Task identifier */
|
|
902
|
+
taskId: string;
|
|
903
|
+
/** ISO timestamp of the next scheduled run */
|
|
904
|
+
nextRun: string;
|
|
905
|
+
/** Result of the most recent run, or null if never run */
|
|
906
|
+
lastRun: RunResult | null;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Overall maintenance module status, exposed via dashboard API.
|
|
910
|
+
*/
|
|
911
|
+
interface MaintenanceStatus {
|
|
912
|
+
/** Whether this orchestrator instance is the maintenance leader */
|
|
913
|
+
isLeader: boolean;
|
|
914
|
+
/** ISO timestamp of the last successful leader claim, or null */
|
|
915
|
+
lastLeaderClaim: string | null;
|
|
916
|
+
/** Schedule state for all enabled tasks */
|
|
917
|
+
schedule: ScheduleEntry[];
|
|
918
|
+
/** Currently executing task, or null if idle */
|
|
919
|
+
activeRun: {
|
|
920
|
+
taskId: string;
|
|
921
|
+
startedAt: string;
|
|
922
|
+
} | null;
|
|
923
|
+
/** History of completed runs (most recent first) */
|
|
924
|
+
history: RunResult[];
|
|
925
|
+
}
|
|
926
|
+
|
|
417
927
|
/**
|
|
418
928
|
* The central orchestrator that manages the lifecycle of coding agents.
|
|
419
929
|
*
|
|
@@ -434,7 +944,28 @@ declare class Orchestrator extends EventEmitter {
|
|
|
434
944
|
private promptTemplate;
|
|
435
945
|
private server?;
|
|
436
946
|
private interval?;
|
|
947
|
+
private heartbeatInterval?;
|
|
437
948
|
private logger;
|
|
949
|
+
private interactionQueue;
|
|
950
|
+
private localRunner;
|
|
951
|
+
private pipeline;
|
|
952
|
+
private analysisArchive;
|
|
953
|
+
private graphStore;
|
|
954
|
+
private claimManager;
|
|
955
|
+
private prDetector;
|
|
956
|
+
private maintenanceScheduler;
|
|
957
|
+
private maintenanceReporter;
|
|
958
|
+
private orchestratorIdPromise;
|
|
959
|
+
/** Project root directory, derived from workspace root. */
|
|
960
|
+
private get projectRoot();
|
|
961
|
+
private graphLoaded;
|
|
962
|
+
private enrichedSpecsByIssue;
|
|
963
|
+
/** Tracks recently-failed intelligence analysis to avoid re-requesting every tick */
|
|
964
|
+
private analysisFailureCache;
|
|
965
|
+
/** Guards against overlapping ticks when a tick takes longer than the polling interval */
|
|
966
|
+
private tickInProgress;
|
|
967
|
+
/** Current tick-phase activity visible to the dashboard */
|
|
968
|
+
private tickActivity;
|
|
438
969
|
/**
|
|
439
970
|
* Creates a new Orchestrator instance.
|
|
440
971
|
*
|
|
@@ -445,15 +976,71 @@ declare class Orchestrator extends EventEmitter {
|
|
|
445
976
|
constructor(config: WorkflowConfig, promptTemplate: string, overrides?: {
|
|
446
977
|
tracker?: IssueTrackerClient;
|
|
447
978
|
backend?: AgentBackend;
|
|
979
|
+
execFileFn?: ExecFileFn;
|
|
448
980
|
});
|
|
449
981
|
private createTracker;
|
|
450
982
|
private createBackend;
|
|
451
983
|
/**
|
|
452
|
-
*
|
|
984
|
+
* Creates a TaskRunner for the maintenance scheduler.
|
|
985
|
+
* Provides stub implementations for check/agent/command execution.
|
|
986
|
+
* Phase 4 (PRManager) and Phase 5 (Reporter) will enhance these.
|
|
987
|
+
*/
|
|
988
|
+
private createMaintenanceTaskRunner;
|
|
989
|
+
/**
|
|
990
|
+
* Initializes the maintenance subsystem: reporter, scheduler, and server route wiring.
|
|
991
|
+
* Extracted from start() to keep function length under threshold.
|
|
992
|
+
*/
|
|
993
|
+
private initMaintenance;
|
|
994
|
+
private createLocalBackend;
|
|
995
|
+
private createIntelligencePipeline;
|
|
996
|
+
/**
|
|
997
|
+
* Create the AnalysisProvider for the intelligence pipeline.
|
|
998
|
+
*
|
|
999
|
+
* Resolution order:
|
|
1000
|
+
* 1. Explicit `intelligence.provider` config (separate key/endpoint)
|
|
1001
|
+
* 2. Local backend config (agent.localBackend + localEndpoint/localModel)
|
|
1002
|
+
* 3. Primary agent backend config (agent.apiKey + agent.backend)
|
|
1003
|
+
*/
|
|
1004
|
+
private createAnalysisProvider;
|
|
1005
|
+
private createProviderFromExplicitConfig;
|
|
1006
|
+
private runPeslSimulations;
|
|
1007
|
+
private archiveAnalysisResults;
|
|
1008
|
+
/**
|
|
1009
|
+
* Auto-publish analysis results to the external tracker as structured comments.
|
|
1010
|
+
* Fires only when:
|
|
1011
|
+
* - A tracker is configured in harness.config.json (roadmap.tracker)
|
|
1012
|
+
* - GITHUB_TOKEN env var is available
|
|
1013
|
+
* - The record has a non-null externalId
|
|
1014
|
+
* - The record has not already been published (per the published index)
|
|
453
1015
|
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
1016
|
+
* Errors are non-fatal: a failed publish logs a warning but does not block
|
|
1017
|
+
* the orchestrator tick.
|
|
456
1018
|
*/
|
|
1019
|
+
private autoPublishAnalyses;
|
|
1020
|
+
private publishAnalysisForIssue;
|
|
1021
|
+
private runIntelligencePipeline;
|
|
1022
|
+
/**
|
|
1023
|
+
* Analyzes a single candidate issue through the intelligence pipeline.
|
|
1024
|
+
* Returns connection error state and whether the circuit breaker tripped.
|
|
1025
|
+
*/
|
|
1026
|
+
private analyzeCandidate;
|
|
1027
|
+
private evictExpiredFailures;
|
|
1028
|
+
private filterEligibleForAnalysis;
|
|
1029
|
+
private analyzeCandidates;
|
|
1030
|
+
/**
|
|
1031
|
+
* Lazily initializes the ClaimManager if it hasn't been created yet.
|
|
1032
|
+
* Called from both start() and asyncTick() to avoid duplicating the init block.
|
|
1033
|
+
*/
|
|
1034
|
+
private ensureClaimManager;
|
|
1035
|
+
/**
|
|
1036
|
+
* Loads the graph store and hydrates the enriched spec cache from the analysis
|
|
1037
|
+
* archive on the first tick. Subsequent calls are no-ops. All failures are
|
|
1038
|
+
* non-fatal — empty graph / empty cache are valid fallbacks.
|
|
1039
|
+
*/
|
|
1040
|
+
private loadPersistedData;
|
|
1041
|
+
private loadGraphStore;
|
|
1042
|
+
private hydrateSpecCache;
|
|
1043
|
+
asyncTick(): Promise<void>;
|
|
457
1044
|
tick(): Promise<void>;
|
|
458
1045
|
/**
|
|
459
1046
|
* Processes a side effect generated by the state machine.
|
|
@@ -461,6 +1048,53 @@ declare class Orchestrator extends EventEmitter {
|
|
|
461
1048
|
* @param effect - The effect to handle
|
|
462
1049
|
*/
|
|
463
1050
|
private handleEffect;
|
|
1051
|
+
/**
|
|
1052
|
+
* Guards workspace cleanup by checking whether the agent pushed a branch
|
|
1053
|
+
* that does not yet have a pull request. If so, the worktree is preserved
|
|
1054
|
+
* and an interaction is queued so a human can create the PR manually.
|
|
1055
|
+
*/
|
|
1056
|
+
private cleanWorkspaceWithGuard;
|
|
1057
|
+
/**
|
|
1058
|
+
* Delegates to PRDetector.branchHasPullRequest.
|
|
1059
|
+
* @see PRDetector#branchHasPullRequest
|
|
1060
|
+
*/
|
|
1061
|
+
private branchHasPullRequest;
|
|
1062
|
+
/**
|
|
1063
|
+
* Delegates to PRDetector.filterCandidatesWithOpenPRs.
|
|
1064
|
+
* @see PRDetector#filterCandidatesWithOpenPRs
|
|
1065
|
+
*/
|
|
1066
|
+
private filterCandidatesWithOpenPRs;
|
|
1067
|
+
/**
|
|
1068
|
+
* Scans candidate issues for stale claims from other orchestrators.
|
|
1069
|
+
* An issue is considered stale if:
|
|
1070
|
+
* - It is in an "in-progress" state
|
|
1071
|
+
* - It has an assignee that is NOT this orchestrator
|
|
1072
|
+
* - Its updatedAt timestamp exceeds the heartbeat TTL
|
|
1073
|
+
*
|
|
1074
|
+
* Stale claims are released so the issue becomes available on subsequent ticks.
|
|
1075
|
+
*/
|
|
1076
|
+
private releaseStaleClaims;
|
|
1077
|
+
/**
|
|
1078
|
+
* Handles an escalation effect by writing to the interaction queue and logging.
|
|
1079
|
+
*/
|
|
1080
|
+
private handleEscalation;
|
|
1081
|
+
/**
|
|
1082
|
+
* Handles a claim effect by calling claimAndVerify on the ClaimManager.
|
|
1083
|
+
* If claimed, proceeds to dispatch. If rejected, emits a claim_rejected
|
|
1084
|
+
* event to clean up the state machine.
|
|
1085
|
+
*/
|
|
1086
|
+
private handleClaimEffect;
|
|
1087
|
+
/**
|
|
1088
|
+
* Posts a structured comment on the GitHub issue when the orchestrator claims it.
|
|
1089
|
+
* Fire-and-forget: failures are logged but never block dispatch.
|
|
1090
|
+
*/
|
|
1091
|
+
private postClaimComment;
|
|
1092
|
+
/**
|
|
1093
|
+
* Posts a lifecycle event comment to the GitHub issue.
|
|
1094
|
+
* Supports: claimed, completed, released.
|
|
1095
|
+
* Fire-and-forget: failures are logged but never block the caller.
|
|
1096
|
+
*/
|
|
1097
|
+
private postLifecycleComment;
|
|
464
1098
|
/**
|
|
465
1099
|
* Dispatches a new agent to work on an issue.
|
|
466
1100
|
*
|
|
@@ -468,10 +1102,9 @@ declare class Orchestrator extends EventEmitter {
|
|
|
468
1102
|
* @param attempt - The retry attempt number
|
|
469
1103
|
*/
|
|
470
1104
|
private dispatchIssue;
|
|
471
|
-
/**
|
|
472
|
-
* Runs an agent session in a background task to avoid blocking the main loop.
|
|
473
|
-
*/
|
|
474
1105
|
private runAgentInBackgroundTask;
|
|
1106
|
+
private recordOutcomeIfPipelineEnabled;
|
|
1107
|
+
private handleCompletionSideEffects;
|
|
475
1108
|
/**
|
|
476
1109
|
* Informs the state machine that an agent worker has exited.
|
|
477
1110
|
*/
|
|
@@ -482,26 +1115,39 @@ declare class Orchestrator extends EventEmitter {
|
|
|
482
1115
|
* @param issueId - The ID of the issue to stop
|
|
483
1116
|
*/
|
|
484
1117
|
private stopIssue;
|
|
1118
|
+
/**
|
|
1119
|
+
* Dispatch a work item immediately, bypassing the normal tick → roadmap cycle.
|
|
1120
|
+
* Used by the dashboard's "Dispatch Now" action.
|
|
1121
|
+
*/
|
|
1122
|
+
dispatchAdHoc(issue: Issue): Promise<void>;
|
|
485
1123
|
/**
|
|
486
1124
|
* Starts the polling loop and the internal HTTP server.
|
|
1125
|
+
* Runs startup reconciliation to release orphaned claims before the first tick.
|
|
487
1126
|
*/
|
|
488
|
-
start(): void
|
|
1127
|
+
start(): Promise<void>;
|
|
489
1128
|
/**
|
|
490
1129
|
* Stops the orchestrator, clearing the polling interval and stopping the server.
|
|
491
1130
|
*/
|
|
492
1131
|
stop(): Promise<void>;
|
|
1132
|
+
/** Update tick activity and broadcast the change to connected clients. */
|
|
1133
|
+
private setTickActivity;
|
|
493
1134
|
/**
|
|
494
1135
|
* Returns a point-in-time snapshot of the orchestrator's internal state.
|
|
495
1136
|
*/
|
|
496
1137
|
getSnapshot(): Record<string, unknown>;
|
|
1138
|
+
/** Returns the maintenance scheduler status, or null if maintenance is not enabled. */
|
|
1139
|
+
getMaintenanceStatus(): MaintenanceStatus | null;
|
|
497
1140
|
}
|
|
498
1141
|
|
|
499
1142
|
/**
|
|
500
1143
|
* Launches the Ink TUI for the given Orchestrator instance.
|
|
501
1144
|
* Returns a function to wait for the TUI to exit.
|
|
1145
|
+
*
|
|
1146
|
+
* @deprecated The TUI is maintained as a fallback for headless/SSH environments.
|
|
1147
|
+
* The web dashboard (served at port 8080) is the primary monitoring interface.
|
|
502
1148
|
*/
|
|
503
1149
|
declare function launchTUI(orchestrator: Orchestrator): {
|
|
504
1150
|
waitUntilExit: () => Promise<void>;
|
|
505
1151
|
};
|
|
506
1152
|
|
|
507
|
-
export { type AgentUpdateEvent,
|
|
1153
|
+
export { type AgentUpdateEvent, AnalysisArchive, type AnalysisRecord, type ApplyEventResult, type ArtifactPresence, ClaimManager, type ClaimManagerConfig, type CleanWorkspaceEffect, type DispatchEffect, type EmitLogEffect, type EscalateEffect, type ExecFileFn, InteractionQueue, type LinearGraphQLExtension, LinearGraphQLStub, type LiveSession, MockBackend, ORCHESTRATOR_IDENTITY_FILE, Orchestrator, type OrchestratorEvent, type OrchestratorState, PRDetector, type PRDetectorLogger, type PendingInteraction, PromptRenderer, type PublishedIndex, type RateLimitSnapshot as RateLimitComputeSnapshot, type RateLimitConfig, type RateLimitSnapshot$1 as RateLimitSnapshot, type ReleaseClaimEffect, type RetryEntry, type RetryFiredEvent, RoadmapTrackerAdapter, type RunAttemptPhase, type RunningEntry, type ScheduleRetryEffect, type SideEffect, type StallDetectedEvent, type StopEffect, type TickEvent, type TokenTotals, type TriageConfig, type TriageDecision, type TriageSignals, type TriageSkill, type UpdateTokensEffect, type WorkerExitEvent, WorkflowLoader, WorkspaceHooks, WorkspaceManager, applyEvent, artifactPresenceFromIssue, calculateRetryDelay, canDispatch, computeRateLimitDelay, createEmptyState, detectScopeTier, extractTitlePrefix, getAvailableSlots, getDefaultConfig, getPerStateCount, isEligible, launchTUI, loadPublishedIndex, reconcile, renderAnalysisComment, resolveEscalationConfig, resolveOrchestratorId, routeIssue, savePublishedIndex, selectCandidates, sortCandidates, triageIssue, validateWorkflowConfig };
|