@harness-engineering/orchestrator 0.3.2 → 0.4.1
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 +114 -66
- package/dist/index.d.ts +114 -66
- package/dist/index.js +768 -468
- package/dist/index.mjs +781 -467
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -72,6 +72,7 @@ __export(index_exports, {
|
|
|
72
72
|
savePublishedIndex: () => savePublishedIndex,
|
|
73
73
|
selectCandidates: () => selectCandidates,
|
|
74
74
|
sortCandidates: () => sortCandidates,
|
|
75
|
+
syncMain: () => syncMain,
|
|
75
76
|
triageIssue: () => triageIssue,
|
|
76
77
|
validateWorkflowConfig: () => validateWorkflowConfig
|
|
77
78
|
});
|
|
@@ -649,6 +650,56 @@ function reconcileCompletedAndClaimed(next, candidates, nowMs, effects) {
|
|
|
649
650
|
}
|
|
650
651
|
}
|
|
651
652
|
}
|
|
653
|
+
function gatherSignalsAndPersona(issue, event) {
|
|
654
|
+
const signals = [...event.concernSignals?.get(issue.id) ?? []];
|
|
655
|
+
let suggestedPersona;
|
|
656
|
+
try {
|
|
657
|
+
const personaRecs = event.personaRecommendations?.get(issue.id);
|
|
658
|
+
if (personaRecs && personaRecs.length > 0) {
|
|
659
|
+
suggestedPersona = personaRecs[0].persona;
|
|
660
|
+
if (personaRecs[0].weightedScore < 0.3) {
|
|
661
|
+
signals.push({
|
|
662
|
+
name: "lowExpertise",
|
|
663
|
+
reason: `Top persona "${suggestedPersona}" scored ${personaRecs[0].weightedScore.toFixed(2)} (below 0.3 threshold)`
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
} else if (personaRecs && personaRecs.length === 0) {
|
|
667
|
+
signals.push({
|
|
668
|
+
name: "noPersonaMatch",
|
|
669
|
+
reason: "No persona recommendations available for this issue's systems"
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
return { signals, suggestedPersona };
|
|
675
|
+
}
|
|
676
|
+
function attachPersonaToLastClaim(effects, suggestedPersona) {
|
|
677
|
+
if (!suggestedPersona) return;
|
|
678
|
+
const lastEffect = effects[effects.length - 1];
|
|
679
|
+
if (lastEffect && lastEffect.type === "claim") {
|
|
680
|
+
lastEffect.suggestedPersona = suggestedPersona;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function dispatchEligibleIssue(next, issue, event, escalationConfig, config, effects) {
|
|
684
|
+
const scopeTier = detectScopeTier(issue, artifactPresenceFromIssue(issue));
|
|
685
|
+
const { signals, suggestedPersona } = gatherSignalsAndPersona(issue, event);
|
|
686
|
+
const decision = routeIssue(scopeTier, signals, escalationConfig);
|
|
687
|
+
if (decision.action === "needs-human") {
|
|
688
|
+
next.claimed.add(issue.id);
|
|
689
|
+
effects.push(
|
|
690
|
+
buildEscalateEffect(issue.id, issue.identifier, decision.reasons, {
|
|
691
|
+
issueTitle: issue.title,
|
|
692
|
+
issueDescription: issue.description,
|
|
693
|
+
enrichedSpec: event.enrichedSpecs?.get(issue.id),
|
|
694
|
+
complexityScore: event.complexityScores?.get(issue.id)
|
|
695
|
+
})
|
|
696
|
+
);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const backend = resolveBackend(decision.action, !!config.agent.localBackend);
|
|
700
|
+
claimAndDispatch(next, issue, backend, event.nowMs, effects);
|
|
701
|
+
attachPersonaToLastClaim(effects, suggestedPersona);
|
|
702
|
+
}
|
|
652
703
|
function handleTick(state, event, config) {
|
|
653
704
|
const { candidates, runningStates, nowMs } = event;
|
|
654
705
|
const next = cloneState(state);
|
|
@@ -679,48 +730,7 @@ function handleTick(state, event, config) {
|
|
|
679
730
|
effects.push(peslAbort);
|
|
680
731
|
continue;
|
|
681
732
|
}
|
|
682
|
-
|
|
683
|
-
const signals = [...event.concernSignals?.get(issue.id) ?? []];
|
|
684
|
-
let suggestedPersona;
|
|
685
|
-
try {
|
|
686
|
-
const personaRecs = event.personaRecommendations?.get(issue.id);
|
|
687
|
-
if (personaRecs && personaRecs.length > 0) {
|
|
688
|
-
suggestedPersona = personaRecs[0].persona;
|
|
689
|
-
if (personaRecs[0].weightedScore < 0.3) {
|
|
690
|
-
signals.push({
|
|
691
|
-
name: "lowExpertise",
|
|
692
|
-
reason: `Top persona "${suggestedPersona}" scored ${personaRecs[0].weightedScore.toFixed(2)} (below 0.3 threshold)`
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
} else if (personaRecs && personaRecs.length === 0) {
|
|
696
|
-
signals.push({
|
|
697
|
-
name: "noPersonaMatch",
|
|
698
|
-
reason: "No persona recommendations available for this issue's systems"
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
|
-
} catch {
|
|
702
|
-
}
|
|
703
|
-
const decision = routeIssue(scopeTier, signals, escalationConfig);
|
|
704
|
-
if (decision.action === "needs-human") {
|
|
705
|
-
next.claimed.add(issue.id);
|
|
706
|
-
effects.push(
|
|
707
|
-
buildEscalateEffect(issue.id, issue.identifier, decision.reasons, {
|
|
708
|
-
issueTitle: issue.title,
|
|
709
|
-
issueDescription: issue.description,
|
|
710
|
-
enrichedSpec: event.enrichedSpecs?.get(issue.id),
|
|
711
|
-
complexityScore: event.complexityScores?.get(issue.id)
|
|
712
|
-
})
|
|
713
|
-
);
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
const backend = resolveBackend(decision.action, !!config.agent.localBackend);
|
|
717
|
-
claimAndDispatch(next, issue, backend, nowMs, effects);
|
|
718
|
-
if (suggestedPersona) {
|
|
719
|
-
const lastEffect = effects[effects.length - 1];
|
|
720
|
-
if (lastEffect && lastEffect.type === "claim") {
|
|
721
|
-
lastEffect.suggestedPersona = suggestedPersona;
|
|
722
|
-
}
|
|
723
|
-
}
|
|
733
|
+
dispatchEligibleIssue(next, issue, event, escalationConfig, config, effects);
|
|
724
734
|
}
|
|
725
735
|
pruneCompleted(next);
|
|
726
736
|
return { nextState: next, effects };
|
|
@@ -1916,11 +1926,11 @@ var BackendsMapSchema = import_zod2.z.record(import_zod2.z.string(), BackendDefS
|
|
|
1916
1926
|
function crossFieldRoutingIssues(backends, routing) {
|
|
1917
1927
|
const issues = [];
|
|
1918
1928
|
const names = new Set(Object.keys(backends));
|
|
1919
|
-
const checkRef = (
|
|
1929
|
+
const checkRef = (path17, name) => {
|
|
1920
1930
|
if (name !== void 0 && !names.has(name)) {
|
|
1921
1931
|
issues.push({
|
|
1922
|
-
path:
|
|
1923
|
-
message: `routing.${
|
|
1932
|
+
path: path17,
|
|
1933
|
+
message: `routing.${path17.join(".")} references unknown backend '${name}'. Defined: [${[...names].join(", ")}].`
|
|
1924
1934
|
});
|
|
1925
1935
|
}
|
|
1926
1936
|
};
|
|
@@ -2279,8 +2289,11 @@ var WorkspaceManager = class {
|
|
|
2279
2289
|
config;
|
|
2280
2290
|
/** Absolute path to the git repository root (resolved lazily). */
|
|
2281
2291
|
repoRoot = null;
|
|
2282
|
-
|
|
2292
|
+
/** Phase 3 (D6): emit baseref_fallback when fallback chain selects a local-only ref. */
|
|
2293
|
+
emitEvent;
|
|
2294
|
+
constructor(config, options = {}) {
|
|
2283
2295
|
this.config = config;
|
|
2296
|
+
this.emitEvent = options.emitEvent ?? null;
|
|
2284
2297
|
}
|
|
2285
2298
|
/** Runs a git command and returns stdout. Extracted for testability. */
|
|
2286
2299
|
async git(args, cwd) {
|
|
@@ -2365,9 +2378,14 @@ var WorkspaceManager = class {
|
|
|
2365
2378
|
* Priority order:
|
|
2366
2379
|
* 1. `config.baseRef` (explicit override). Throws if it doesn't resolve.
|
|
2367
2380
|
* 2. Default branch via `git symbolic-ref --short refs/remotes/origin/HEAD`.
|
|
2368
|
-
* 3.
|
|
2369
|
-
* 4.
|
|
2370
|
-
*
|
|
2381
|
+
* 3. Remote fallbacks: `origin/main`, `origin/master`. (No event.)
|
|
2382
|
+
* 4. Local-only fallbacks: `main`, `master`. (Emits `baseref_fallback`.)
|
|
2383
|
+
* 5. `HEAD` as ultimate fallback. (Emits `baseref_fallback`.)
|
|
2384
|
+
*
|
|
2385
|
+
* Phase 3 / spec D6 / R4: when the priority chain falls past `origin/*`
|
|
2386
|
+
* to a local-only ref, the optional `emitEvent` callback (if injected)
|
|
2387
|
+
* is invoked exactly once with `{ kind: 'baseref_fallback', ref, repoRoot }`
|
|
2388
|
+
* so operators are warned when the remote is misconfigured or unreachable.
|
|
2371
2389
|
*/
|
|
2372
2390
|
async resolveBaseRef(repoRoot) {
|
|
2373
2391
|
const configured = this.config.baseRef;
|
|
@@ -2386,11 +2404,30 @@ var WorkspaceManager = class {
|
|
|
2386
2404
|
if (detected) return detected;
|
|
2387
2405
|
} catch {
|
|
2388
2406
|
}
|
|
2389
|
-
for (const candidate of ["origin/main", "origin/master"
|
|
2407
|
+
for (const candidate of ["origin/main", "origin/master"]) {
|
|
2390
2408
|
if (await this.refExists(candidate, repoRoot)) return candidate;
|
|
2391
2409
|
}
|
|
2410
|
+
for (const candidate of ["main", "master"]) {
|
|
2411
|
+
if (await this.refExists(candidate, repoRoot)) {
|
|
2412
|
+
this.emitFallback(candidate, repoRoot);
|
|
2413
|
+
return candidate;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
this.emitFallback("HEAD", repoRoot);
|
|
2392
2417
|
return "HEAD";
|
|
2393
2418
|
}
|
|
2419
|
+
/**
|
|
2420
|
+
* Phase 3 (D6): emit a `baseref_fallback` event via the injected
|
|
2421
|
+
* callback (if any). Errors from the callback are swallowed so a
|
|
2422
|
+
* broken emitter does not block worktree dispatch.
|
|
2423
|
+
*/
|
|
2424
|
+
emitFallback(ref, repoRoot) {
|
|
2425
|
+
if (!this.emitEvent) return;
|
|
2426
|
+
try {
|
|
2427
|
+
this.emitEvent({ kind: "baseref_fallback", ref, repoRoot });
|
|
2428
|
+
} catch {
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2394
2431
|
/** Returns true iff `git rev-parse --verify` accepts the ref. */
|
|
2395
2432
|
async refExists(ref, repoRoot) {
|
|
2396
2433
|
try {
|
|
@@ -2672,11 +2709,9 @@ var PromptRenderer = class {
|
|
|
2672
2709
|
|
|
2673
2710
|
// src/orchestrator.ts
|
|
2674
2711
|
var import_node_events = require("events");
|
|
2675
|
-
var
|
|
2712
|
+
var path16 = __toESM(require("path"));
|
|
2676
2713
|
var import_node_crypto7 = require("crypto");
|
|
2677
|
-
var
|
|
2678
|
-
var import_intelligence4 = require("@harness-engineering/intelligence");
|
|
2679
|
-
var import_graph = require("@harness-engineering/graph");
|
|
2714
|
+
var import_core10 = require("@harness-engineering/core");
|
|
2680
2715
|
|
|
2681
2716
|
// src/intelligence/pipeline-runner.ts
|
|
2682
2717
|
var path7 = __toESM(require("path"));
|
|
@@ -3237,7 +3272,83 @@ var CompletionHandler = class {
|
|
|
3237
3272
|
};
|
|
3238
3273
|
|
|
3239
3274
|
// src/orchestrator.ts
|
|
3240
|
-
var
|
|
3275
|
+
var import_core11 = require("@harness-engineering/core");
|
|
3276
|
+
|
|
3277
|
+
// src/tracker/adapters/github-issues-issue-tracker.ts
|
|
3278
|
+
var import_types9 = require("@harness-engineering/types");
|
|
3279
|
+
var GitHubIssuesIssueTrackerAdapter = class {
|
|
3280
|
+
client;
|
|
3281
|
+
config;
|
|
3282
|
+
constructor(client, config) {
|
|
3283
|
+
this.client = client;
|
|
3284
|
+
this.config = config;
|
|
3285
|
+
}
|
|
3286
|
+
async fetchCandidateIssues() {
|
|
3287
|
+
return this.fetchIssuesByStates(this.config.activeStates);
|
|
3288
|
+
}
|
|
3289
|
+
async fetchIssuesByStates(stateNames) {
|
|
3290
|
+
const r = await this.client.fetchByStatus(
|
|
3291
|
+
stateNames
|
|
3292
|
+
);
|
|
3293
|
+
if (!r.ok) return (0, import_types9.Err)(r.error);
|
|
3294
|
+
return (0, import_types9.Ok)(r.value.map((f) => this.mapTrackedToIssue(f)));
|
|
3295
|
+
}
|
|
3296
|
+
async fetchIssueStatesByIds(issueIds) {
|
|
3297
|
+
const r = await this.client.fetchAll();
|
|
3298
|
+
if (!r.ok) return (0, import_types9.Err)(r.error);
|
|
3299
|
+
const wanted = new Set(issueIds);
|
|
3300
|
+
const out = /* @__PURE__ */ new Map();
|
|
3301
|
+
for (const f of r.value.features) {
|
|
3302
|
+
if (wanted.has(f.externalId)) out.set(f.externalId, this.mapTrackedToIssue(f));
|
|
3303
|
+
}
|
|
3304
|
+
return (0, import_types9.Ok)(out);
|
|
3305
|
+
}
|
|
3306
|
+
async claimIssue(issueId, orchestratorId) {
|
|
3307
|
+
const r = await this.client.claim(issueId, orchestratorId);
|
|
3308
|
+
if (!r.ok) return (0, import_types9.Err)(r.error);
|
|
3309
|
+
return (0, import_types9.Ok)(void 0);
|
|
3310
|
+
}
|
|
3311
|
+
async releaseIssue(issueId) {
|
|
3312
|
+
const r = await this.client.release(issueId);
|
|
3313
|
+
if (!r.ok) return (0, import_types9.Err)(r.error);
|
|
3314
|
+
return (0, import_types9.Ok)(void 0);
|
|
3315
|
+
}
|
|
3316
|
+
async markIssueComplete(issueId) {
|
|
3317
|
+
const r = await this.client.complete(issueId);
|
|
3318
|
+
if (!r.ok) return (0, import_types9.Err)(r.error);
|
|
3319
|
+
return (0, import_types9.Ok)(void 0);
|
|
3320
|
+
}
|
|
3321
|
+
/**
|
|
3322
|
+
* Project a wide-interface `TrackedFeature` onto the small-interface
|
|
3323
|
+
* `Issue` shape consumed by the orchestrator's tick loop.
|
|
3324
|
+
*/
|
|
3325
|
+
mapTrackedToIssue(f) {
|
|
3326
|
+
return {
|
|
3327
|
+
id: f.externalId,
|
|
3328
|
+
identifier: f.externalId,
|
|
3329
|
+
title: f.name,
|
|
3330
|
+
description: f.summary,
|
|
3331
|
+
priority: null,
|
|
3332
|
+
state: f.status,
|
|
3333
|
+
branchName: null,
|
|
3334
|
+
url: null,
|
|
3335
|
+
labels: [],
|
|
3336
|
+
spec: f.spec,
|
|
3337
|
+
plans: f.plans,
|
|
3338
|
+
blockedBy: f.blockedBy.map(
|
|
3339
|
+
(b) => ({
|
|
3340
|
+
id: null,
|
|
3341
|
+
identifier: b,
|
|
3342
|
+
state: null
|
|
3343
|
+
})
|
|
3344
|
+
),
|
|
3345
|
+
createdAt: f.createdAt,
|
|
3346
|
+
updatedAt: f.updatedAt,
|
|
3347
|
+
externalId: f.externalId,
|
|
3348
|
+
assignee: f.assignee
|
|
3349
|
+
};
|
|
3350
|
+
}
|
|
3351
|
+
};
|
|
3241
3352
|
|
|
3242
3353
|
// src/agent/runner.ts
|
|
3243
3354
|
var MAX_SLEEP_MS = 12 * 60 * 6e4;
|
|
@@ -3539,9 +3650,17 @@ var LocalModelResolver = class {
|
|
|
3539
3650
|
|
|
3540
3651
|
// src/agent/config-migration.ts
|
|
3541
3652
|
var MIGRATION_GUIDE = "docs/guides/multi-backend-routing.md";
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3653
|
+
var CASE1_ALWAYS_SUPPRESS = /* @__PURE__ */ new Set(["agent.backend"]);
|
|
3654
|
+
var CASE1_LOCAL_GROUP = /* @__PURE__ */ new Set([
|
|
3655
|
+
"agent.localBackend",
|
|
3656
|
+
"agent.localEndpoint",
|
|
3657
|
+
"agent.localModel",
|
|
3658
|
+
"agent.localApiKey",
|
|
3659
|
+
"agent.localTimeoutMs",
|
|
3660
|
+
"agent.localProbeIntervalMs"
|
|
3661
|
+
]);
|
|
3662
|
+
function detectLegacyFields(agent) {
|
|
3663
|
+
const fields = [
|
|
3545
3664
|
{ path: "agent.backend", present: agent.backend !== void 0 && agent.backend !== "" },
|
|
3546
3665
|
{ path: "agent.command", present: agent.command !== void 0 },
|
|
3547
3666
|
{ path: "agent.model", present: agent.model !== void 0 },
|
|
@@ -3553,56 +3672,73 @@ function migrateAgentConfig(agent) {
|
|
|
3553
3672
|
{ path: "agent.localTimeoutMs", present: agent.localTimeoutMs !== void 0 },
|
|
3554
3673
|
{ path: "agent.localProbeIntervalMs", present: agent.localProbeIntervalMs !== void 0 }
|
|
3555
3674
|
];
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
const suppressLocalGroup = agent.localBackend !== void 0;
|
|
3567
|
-
if (agent.backends !== void 0) {
|
|
3568
|
-
for (const path16 of presentLegacy) {
|
|
3569
|
-
if (CASE1_ALWAYS_SUPPRESS.has(path16)) continue;
|
|
3570
|
-
if (suppressLocalGroup && CASE1_LOCAL_GROUP.has(path16)) continue;
|
|
3571
|
-
warnings.push(
|
|
3572
|
-
`Ignoring legacy field '${path16}': 'agent.backends' is set and takes precedence. See ${MIGRATION_GUIDE}.`
|
|
3573
|
-
);
|
|
3574
|
-
}
|
|
3575
|
-
return { config: agent, warnings };
|
|
3576
|
-
}
|
|
3577
|
-
if (presentLegacy.length === 0) {
|
|
3578
|
-
return { config: agent, warnings };
|
|
3675
|
+
return fields.filter((f) => f.present).map((f) => f.path);
|
|
3676
|
+
}
|
|
3677
|
+
function buildCase1Warnings(presentLegacy, suppressLocalGroup) {
|
|
3678
|
+
const warnings = [];
|
|
3679
|
+
for (const path17 of presentLegacy) {
|
|
3680
|
+
if (CASE1_ALWAYS_SUPPRESS.has(path17)) continue;
|
|
3681
|
+
if (suppressLocalGroup && CASE1_LOCAL_GROUP.has(path17)) continue;
|
|
3682
|
+
warnings.push(
|
|
3683
|
+
`Ignoring legacy field '${path17}': 'agent.backends' is set and takes precedence. See ${MIGRATION_GUIDE}.`
|
|
3684
|
+
);
|
|
3579
3685
|
}
|
|
3580
|
-
|
|
3686
|
+
return warnings;
|
|
3687
|
+
}
|
|
3688
|
+
function synthesizeBackendsAndRouting(agent) {
|
|
3689
|
+
const backends = { primary: synthesizePrimary(agent) };
|
|
3581
3690
|
const routing = { default: "primary" };
|
|
3582
|
-
backends.primary = synthesizePrimary(agent);
|
|
3583
3691
|
if (agent.localBackend !== void 0) {
|
|
3584
3692
|
backends.local = synthesizeLocal(agent);
|
|
3693
|
+
const autoExec = agent.escalation?.autoExecute ?? [];
|
|
3694
|
+
for (const tier of autoExec) routing[tier] = "local";
|
|
3585
3695
|
}
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3696
|
+
return { backends, routing };
|
|
3697
|
+
}
|
|
3698
|
+
function migrateAgentConfig(agent) {
|
|
3699
|
+
const presentLegacy = detectLegacyFields(agent);
|
|
3700
|
+
if (agent.backends !== void 0) {
|
|
3701
|
+
return {
|
|
3702
|
+
config: agent,
|
|
3703
|
+
warnings: buildCase1Warnings(presentLegacy, agent.localBackend !== void 0)
|
|
3704
|
+
};
|
|
3591
3705
|
}
|
|
3592
|
-
|
|
3593
|
-
warnings
|
|
3594
|
-
`Deprecated config field '${path16}' is in use. Migrate to 'agent.backends' / 'agent.routing'. See ${MIGRATION_GUIDE}.`
|
|
3595
|
-
);
|
|
3706
|
+
if (presentLegacy.length === 0) {
|
|
3707
|
+
return { config: agent, warnings: [] };
|
|
3596
3708
|
}
|
|
3709
|
+
const { backends, routing } = synthesizeBackendsAndRouting(agent);
|
|
3710
|
+
const warnings = presentLegacy.map(
|
|
3711
|
+
(path17) => `Deprecated config field '${path17}' is in use. Migrate to 'agent.backends' / 'agent.routing'. See ${MIGRATION_GUIDE}.`
|
|
3712
|
+
);
|
|
3597
3713
|
return {
|
|
3598
|
-
config: {
|
|
3599
|
-
...agent,
|
|
3600
|
-
backends,
|
|
3601
|
-
routing
|
|
3602
|
-
},
|
|
3714
|
+
config: { ...agent, backends, routing },
|
|
3603
3715
|
warnings
|
|
3604
3716
|
};
|
|
3605
3717
|
}
|
|
3718
|
+
function buildRemoteBackend(type, agent, context) {
|
|
3719
|
+
if (agent.model === void 0) {
|
|
3720
|
+
throw new Error(`migrateAgentConfig: ${context} requires agent.model`);
|
|
3721
|
+
}
|
|
3722
|
+
const def = { type, model: agent.model };
|
|
3723
|
+
if (agent.apiKey !== void 0) def.apiKey = agent.apiKey;
|
|
3724
|
+
return def;
|
|
3725
|
+
}
|
|
3726
|
+
function buildLocalConnectionBackend(type, agent, context) {
|
|
3727
|
+
if (agent.localEndpoint === void 0 || agent.localModel === void 0) {
|
|
3728
|
+
throw new Error(
|
|
3729
|
+
`migrateAgentConfig: ${context} requires agent.localEndpoint and agent.localModel`
|
|
3730
|
+
);
|
|
3731
|
+
}
|
|
3732
|
+
const def = {
|
|
3733
|
+
type,
|
|
3734
|
+
endpoint: agent.localEndpoint,
|
|
3735
|
+
model: agent.localModel
|
|
3736
|
+
};
|
|
3737
|
+
if (agent.localApiKey !== void 0) def.apiKey = agent.localApiKey;
|
|
3738
|
+
if (agent.localTimeoutMs !== void 0) def.timeoutMs = agent.localTimeoutMs;
|
|
3739
|
+
if (agent.localProbeIntervalMs !== void 0) def.probeIntervalMs = agent.localProbeIntervalMs;
|
|
3740
|
+
return def;
|
|
3741
|
+
}
|
|
3606
3742
|
function synthesizePrimary(agent) {
|
|
3607
3743
|
const backend = agent.backend;
|
|
3608
3744
|
switch (backend) {
|
|
@@ -3613,64 +3749,13 @@ function synthesizePrimary(agent) {
|
|
|
3613
3749
|
if (agent.command !== void 0) def.command = agent.command;
|
|
3614
3750
|
return def;
|
|
3615
3751
|
}
|
|
3616
|
-
case "anthropic":
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
}
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
return
|
|
3623
|
-
}
|
|
3624
|
-
case "openai": {
|
|
3625
|
-
if (agent.model === void 0) {
|
|
3626
|
-
throw new Error("migrateAgentConfig: agent.backend='openai' requires agent.model");
|
|
3627
|
-
}
|
|
3628
|
-
const def = { type: "openai", model: agent.model };
|
|
3629
|
-
if (agent.apiKey !== void 0) def.apiKey = agent.apiKey;
|
|
3630
|
-
return def;
|
|
3631
|
-
}
|
|
3632
|
-
case "gemini": {
|
|
3633
|
-
if (agent.model === void 0) {
|
|
3634
|
-
throw new Error("migrateAgentConfig: agent.backend='gemini' requires agent.model");
|
|
3635
|
-
}
|
|
3636
|
-
const def = { type: "gemini", model: agent.model };
|
|
3637
|
-
if (agent.apiKey !== void 0) def.apiKey = agent.apiKey;
|
|
3638
|
-
return def;
|
|
3639
|
-
}
|
|
3640
|
-
case "local": {
|
|
3641
|
-
if (agent.localEndpoint === void 0 || agent.localModel === void 0) {
|
|
3642
|
-
throw new Error(
|
|
3643
|
-
"migrateAgentConfig: agent.backend='local' requires agent.localEndpoint and agent.localModel"
|
|
3644
|
-
);
|
|
3645
|
-
}
|
|
3646
|
-
const def = {
|
|
3647
|
-
type: "local",
|
|
3648
|
-
endpoint: agent.localEndpoint,
|
|
3649
|
-
model: agent.localModel
|
|
3650
|
-
};
|
|
3651
|
-
if (agent.localApiKey !== void 0) def.apiKey = agent.localApiKey;
|
|
3652
|
-
if (agent.localTimeoutMs !== void 0) def.timeoutMs = agent.localTimeoutMs;
|
|
3653
|
-
if (agent.localProbeIntervalMs !== void 0)
|
|
3654
|
-
def.probeIntervalMs = agent.localProbeIntervalMs;
|
|
3655
|
-
return def;
|
|
3656
|
-
}
|
|
3657
|
-
case "pi": {
|
|
3658
|
-
if (agent.localEndpoint === void 0 || agent.localModel === void 0) {
|
|
3659
|
-
throw new Error(
|
|
3660
|
-
"migrateAgentConfig: agent.backend='pi' requires agent.localEndpoint and agent.localModel"
|
|
3661
|
-
);
|
|
3662
|
-
}
|
|
3663
|
-
const def = {
|
|
3664
|
-
type: "pi",
|
|
3665
|
-
endpoint: agent.localEndpoint,
|
|
3666
|
-
model: agent.localModel
|
|
3667
|
-
};
|
|
3668
|
-
if (agent.localApiKey !== void 0) def.apiKey = agent.localApiKey;
|
|
3669
|
-
if (agent.localTimeoutMs !== void 0) def.timeoutMs = agent.localTimeoutMs;
|
|
3670
|
-
if (agent.localProbeIntervalMs !== void 0)
|
|
3671
|
-
def.probeIntervalMs = agent.localProbeIntervalMs;
|
|
3672
|
-
return def;
|
|
3673
|
-
}
|
|
3752
|
+
case "anthropic":
|
|
3753
|
+
case "openai":
|
|
3754
|
+
case "gemini":
|
|
3755
|
+
return buildRemoteBackend(backend, agent, `agent.backend='${backend}'`);
|
|
3756
|
+
case "local":
|
|
3757
|
+
case "pi":
|
|
3758
|
+
return buildLocalConnectionBackend(backend, agent, `agent.backend='${backend}'`);
|
|
3674
3759
|
default:
|
|
3675
3760
|
throw new Error(
|
|
3676
3761
|
`migrateAgentConfig: unknown legacy backend '${String(backend)}'. Expected one of: mock, claude, anthropic, openai, gemini, local, pi.`
|
|
@@ -3681,31 +3766,8 @@ function synthesizeLocal(agent) {
|
|
|
3681
3766
|
if (agent.localBackend === void 0) {
|
|
3682
3767
|
throw new Error("synthesizeLocal called without agent.localBackend");
|
|
3683
3768
|
}
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
"migrateAgentConfig: agent.localBackend requires agent.localEndpoint and agent.localModel"
|
|
3687
|
-
);
|
|
3688
|
-
}
|
|
3689
|
-
if (agent.localBackend === "pi") {
|
|
3690
|
-
const def2 = {
|
|
3691
|
-
type: "pi",
|
|
3692
|
-
endpoint: agent.localEndpoint,
|
|
3693
|
-
model: agent.localModel
|
|
3694
|
-
};
|
|
3695
|
-
if (agent.localApiKey !== void 0) def2.apiKey = agent.localApiKey;
|
|
3696
|
-
if (agent.localTimeoutMs !== void 0) def2.timeoutMs = agent.localTimeoutMs;
|
|
3697
|
-
if (agent.localProbeIntervalMs !== void 0) def2.probeIntervalMs = agent.localProbeIntervalMs;
|
|
3698
|
-
return def2;
|
|
3699
|
-
}
|
|
3700
|
-
const def = {
|
|
3701
|
-
type: "local",
|
|
3702
|
-
endpoint: agent.localEndpoint,
|
|
3703
|
-
model: agent.localModel
|
|
3704
|
-
};
|
|
3705
|
-
if (agent.localApiKey !== void 0) def.apiKey = agent.localApiKey;
|
|
3706
|
-
if (agent.localTimeoutMs !== void 0) def.timeoutMs = agent.localTimeoutMs;
|
|
3707
|
-
if (agent.localProbeIntervalMs !== void 0) def.probeIntervalMs = agent.localProbeIntervalMs;
|
|
3708
|
-
return def;
|
|
3769
|
+
const type = agent.localBackend === "pi" ? "pi" : "local";
|
|
3770
|
+
return buildLocalConnectionBackend(type, agent, "agent.localBackend");
|
|
3709
3771
|
}
|
|
3710
3772
|
|
|
3711
3773
|
// src/agent/backend-router.ts
|
|
@@ -3758,8 +3820,8 @@ var BackendRouter = class {
|
|
|
3758
3820
|
validateReferences() {
|
|
3759
3821
|
const known = new Set(Object.keys(this.backends));
|
|
3760
3822
|
const missing = [];
|
|
3761
|
-
const check = (
|
|
3762
|
-
if (name !== void 0 && !known.has(name)) missing.push({ path:
|
|
3823
|
+
const check = (path17, name) => {
|
|
3824
|
+
if (name !== void 0 && !known.has(name)) missing.push({ path: path17, name });
|
|
3763
3825
|
};
|
|
3764
3826
|
check("default", this.routing.default);
|
|
3765
3827
|
check("quick-fix", this.routing["quick-fix"]);
|
|
@@ -3769,7 +3831,7 @@ var BackendRouter = class {
|
|
|
3769
3831
|
check("intelligence.sel", this.routing.intelligence?.sel);
|
|
3770
3832
|
check("intelligence.pesl", this.routing.intelligence?.pesl);
|
|
3771
3833
|
if (missing.length > 0) {
|
|
3772
|
-
const detail = missing.map(({ path:
|
|
3834
|
+
const detail = missing.map(({ path: path17, name }) => `routing.${path17} -> '${name}'`).join("; ");
|
|
3773
3835
|
const known_ = [...known].join(", ") || "(none)";
|
|
3774
3836
|
throw new Error(
|
|
3775
3837
|
`BackendRouter: routing references unknown backend(s): ${detail}. Defined backends: [${known_}].`
|
|
@@ -3782,13 +3844,13 @@ var BackendRouter = class {
|
|
|
3782
3844
|
var import_node_child_process4 = require("child_process");
|
|
3783
3845
|
var readline = __toESM(require("readline"));
|
|
3784
3846
|
var import_node_crypto3 = require("crypto");
|
|
3785
|
-
var
|
|
3847
|
+
var import_types10 = require("@harness-engineering/types");
|
|
3786
3848
|
function resolveExitCode(code, command, resolve6) {
|
|
3787
3849
|
if (code === 0) {
|
|
3788
|
-
resolve6((0,
|
|
3850
|
+
resolve6((0, import_types10.Ok)(void 0));
|
|
3789
3851
|
} else {
|
|
3790
3852
|
resolve6(
|
|
3791
|
-
(0,
|
|
3853
|
+
(0, import_types10.Err)({
|
|
3792
3854
|
category: "agent_not_found",
|
|
3793
3855
|
message: `Claude command '${command}' not found or failed`
|
|
3794
3856
|
})
|
|
@@ -3796,7 +3858,7 @@ function resolveExitCode(code, command, resolve6) {
|
|
|
3796
3858
|
}
|
|
3797
3859
|
}
|
|
3798
3860
|
function resolveSpawnError(command, resolve6) {
|
|
3799
|
-
resolve6((0,
|
|
3861
|
+
resolve6((0, import_types10.Err)({ category: "agent_not_found", message: `Claude command '${command}' not found` }));
|
|
3800
3862
|
}
|
|
3801
3863
|
var JUST_PAST_GRACE_MS = 5 * 6e4;
|
|
3802
3864
|
var PRIMARY_LIMIT_RE = /You[\u2019']ve hit your limit.*resets\s+(\d{1,2}(?::\d{2})?\s*(?:am|pm))\s*\(([^)]+)\)/i;
|
|
@@ -3995,7 +4057,7 @@ var ClaudeBackend = class {
|
|
|
3995
4057
|
backendName: this.name,
|
|
3996
4058
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3997
4059
|
};
|
|
3998
|
-
return (0,
|
|
4060
|
+
return (0, import_types10.Ok)(session);
|
|
3999
4061
|
}
|
|
4000
4062
|
async *runTurn(session, params) {
|
|
4001
4063
|
const args = [
|
|
@@ -4119,7 +4181,7 @@ var ClaudeBackend = class {
|
|
|
4119
4181
|
};
|
|
4120
4182
|
}
|
|
4121
4183
|
async stopSession(_session) {
|
|
4122
|
-
return (0,
|
|
4184
|
+
return (0, import_types10.Ok)(void 0);
|
|
4123
4185
|
}
|
|
4124
4186
|
async healthCheck() {
|
|
4125
4187
|
return new Promise((resolve6) => {
|
|
@@ -4132,7 +4194,7 @@ var ClaudeBackend = class {
|
|
|
4132
4194
|
|
|
4133
4195
|
// src/agent/backends/anthropic.ts
|
|
4134
4196
|
var import_sdk = __toESM(require("@anthropic-ai/sdk"));
|
|
4135
|
-
var
|
|
4197
|
+
var import_types11 = require("@harness-engineering/types");
|
|
4136
4198
|
var import_core4 = require("@harness-engineering/core");
|
|
4137
4199
|
var AnthropicBackend = class {
|
|
4138
4200
|
name = "anthropic";
|
|
@@ -4150,7 +4212,7 @@ var AnthropicBackend = class {
|
|
|
4150
4212
|
}
|
|
4151
4213
|
async startSession(params) {
|
|
4152
4214
|
if (!this.config.apiKey) {
|
|
4153
|
-
return (0,
|
|
4215
|
+
return (0, import_types11.Err)({
|
|
4154
4216
|
category: "agent_not_found",
|
|
4155
4217
|
message: "ANTHROPIC_API_KEY is not set"
|
|
4156
4218
|
});
|
|
@@ -4162,7 +4224,7 @@ var AnthropicBackend = class {
|
|
|
4162
4224
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4163
4225
|
...params.systemPrompt !== void 0 && { systemPrompt: params.systemPrompt }
|
|
4164
4226
|
};
|
|
4165
|
-
return (0,
|
|
4227
|
+
return (0, import_types11.Ok)(session);
|
|
4166
4228
|
}
|
|
4167
4229
|
async *runTurn(session, params) {
|
|
4168
4230
|
const anthropicSession = session;
|
|
@@ -4227,22 +4289,22 @@ var AnthropicBackend = class {
|
|
|
4227
4289
|
}
|
|
4228
4290
|
}
|
|
4229
4291
|
async stopSession(_session) {
|
|
4230
|
-
return (0,
|
|
4292
|
+
return (0, import_types11.Ok)(void 0);
|
|
4231
4293
|
}
|
|
4232
4294
|
async healthCheck() {
|
|
4233
4295
|
if (!this.config.apiKey) {
|
|
4234
|
-
return (0,
|
|
4296
|
+
return (0, import_types11.Err)({
|
|
4235
4297
|
category: "response_error",
|
|
4236
4298
|
message: "ANTHROPIC_API_KEY is not set"
|
|
4237
4299
|
});
|
|
4238
4300
|
}
|
|
4239
|
-
return (0,
|
|
4301
|
+
return (0, import_types11.Ok)(void 0);
|
|
4240
4302
|
}
|
|
4241
4303
|
};
|
|
4242
4304
|
|
|
4243
4305
|
// src/agent/backends/openai.ts
|
|
4244
4306
|
var import_openai = __toESM(require("openai"));
|
|
4245
|
-
var
|
|
4307
|
+
var import_types12 = require("@harness-engineering/types");
|
|
4246
4308
|
var import_core5 = require("@harness-engineering/core");
|
|
4247
4309
|
var OpenAIBackend = class {
|
|
4248
4310
|
name = "openai";
|
|
@@ -4259,7 +4321,7 @@ var OpenAIBackend = class {
|
|
|
4259
4321
|
}
|
|
4260
4322
|
async startSession(params) {
|
|
4261
4323
|
if (!this.config.apiKey) {
|
|
4262
|
-
return (0,
|
|
4324
|
+
return (0, import_types12.Err)({
|
|
4263
4325
|
category: "agent_not_found",
|
|
4264
4326
|
message: "OPENAI_API_KEY is not set"
|
|
4265
4327
|
});
|
|
@@ -4271,7 +4333,7 @@ var OpenAIBackend = class {
|
|
|
4271
4333
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4272
4334
|
...params.systemPrompt !== void 0 && { systemPrompt: params.systemPrompt }
|
|
4273
4335
|
};
|
|
4274
|
-
return (0,
|
|
4336
|
+
return (0, import_types12.Ok)(session);
|
|
4275
4337
|
}
|
|
4276
4338
|
async *runTurn(session, params) {
|
|
4277
4339
|
const openAISession = session;
|
|
@@ -4347,14 +4409,14 @@ var OpenAIBackend = class {
|
|
|
4347
4409
|
};
|
|
4348
4410
|
}
|
|
4349
4411
|
async stopSession(_session) {
|
|
4350
|
-
return (0,
|
|
4412
|
+
return (0, import_types12.Ok)(void 0);
|
|
4351
4413
|
}
|
|
4352
4414
|
async healthCheck() {
|
|
4353
4415
|
try {
|
|
4354
4416
|
await this.client.models.list();
|
|
4355
|
-
return (0,
|
|
4417
|
+
return (0, import_types12.Ok)(void 0);
|
|
4356
4418
|
} catch (err) {
|
|
4357
|
-
return (0,
|
|
4419
|
+
return (0, import_types12.Err)({
|
|
4358
4420
|
category: "response_error",
|
|
4359
4421
|
message: err instanceof Error ? err.message : "OpenAI health check failed"
|
|
4360
4422
|
});
|
|
@@ -4364,7 +4426,7 @@ var OpenAIBackend = class {
|
|
|
4364
4426
|
|
|
4365
4427
|
// src/agent/backends/gemini.ts
|
|
4366
4428
|
var import_generative_ai = require("@google/generative-ai");
|
|
4367
|
-
var
|
|
4429
|
+
var import_types13 = require("@harness-engineering/types");
|
|
4368
4430
|
var import_core6 = require("@harness-engineering/core");
|
|
4369
4431
|
var GeminiBackend = class {
|
|
4370
4432
|
name = "gemini";
|
|
@@ -4379,7 +4441,7 @@ var GeminiBackend = class {
|
|
|
4379
4441
|
}
|
|
4380
4442
|
async startSession(params) {
|
|
4381
4443
|
if (!this.config.apiKey) {
|
|
4382
|
-
return (0,
|
|
4444
|
+
return (0, import_types13.Err)({
|
|
4383
4445
|
category: "agent_not_found",
|
|
4384
4446
|
message: "GEMINI_API_KEY is not set"
|
|
4385
4447
|
});
|
|
@@ -4391,7 +4453,7 @@ var GeminiBackend = class {
|
|
|
4391
4453
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4392
4454
|
...params.systemPrompt !== void 0 && { systemPrompt: params.systemPrompt }
|
|
4393
4455
|
};
|
|
4394
|
-
return (0,
|
|
4456
|
+
return (0, import_types13.Ok)(session);
|
|
4395
4457
|
}
|
|
4396
4458
|
async *runTurn(session, params) {
|
|
4397
4459
|
const geminiSession = session;
|
|
@@ -4464,15 +4526,15 @@ var GeminiBackend = class {
|
|
|
4464
4526
|
};
|
|
4465
4527
|
}
|
|
4466
4528
|
async stopSession(_session) {
|
|
4467
|
-
return (0,
|
|
4529
|
+
return (0, import_types13.Ok)(void 0);
|
|
4468
4530
|
}
|
|
4469
4531
|
async healthCheck() {
|
|
4470
4532
|
try {
|
|
4471
4533
|
const genAI = new import_generative_ai.GoogleGenerativeAI(this.config.apiKey);
|
|
4472
4534
|
genAI.getGenerativeModel({ model: this.config.model });
|
|
4473
|
-
return (0,
|
|
4535
|
+
return (0, import_types13.Ok)(void 0);
|
|
4474
4536
|
} catch (err) {
|
|
4475
|
-
return (0,
|
|
4537
|
+
return (0, import_types13.Err)({
|
|
4476
4538
|
category: "response_error",
|
|
4477
4539
|
message: err instanceof Error ? err.message : "Gemini health check failed"
|
|
4478
4540
|
});
|
|
@@ -4482,7 +4544,7 @@ var GeminiBackend = class {
|
|
|
4482
4544
|
|
|
4483
4545
|
// src/agent/backends/local.ts
|
|
4484
4546
|
var import_openai2 = __toESM(require("openai"));
|
|
4485
|
-
var
|
|
4547
|
+
var import_types14 = require("@harness-engineering/types");
|
|
4486
4548
|
var DEFAULT_TIMEOUT_MS = 9e4;
|
|
4487
4549
|
var LocalBackend = class {
|
|
4488
4550
|
name = "local";
|
|
@@ -4508,7 +4570,7 @@ var LocalBackend = class {
|
|
|
4508
4570
|
if (this.getModel) {
|
|
4509
4571
|
const candidate = this.getModel();
|
|
4510
4572
|
if (candidate === null) {
|
|
4511
|
-
return (0,
|
|
4573
|
+
return (0, import_types14.Err)({
|
|
4512
4574
|
category: "agent_not_found",
|
|
4513
4575
|
message: "No local model available; check dashboard for details."
|
|
4514
4576
|
});
|
|
@@ -4525,7 +4587,7 @@ var LocalBackend = class {
|
|
|
4525
4587
|
resolvedModel,
|
|
4526
4588
|
...params.systemPrompt !== void 0 && { systemPrompt: params.systemPrompt }
|
|
4527
4589
|
};
|
|
4528
|
-
return (0,
|
|
4590
|
+
return (0, import_types14.Ok)(session);
|
|
4529
4591
|
}
|
|
4530
4592
|
async *runTurn(session, params) {
|
|
4531
4593
|
const localSession = session;
|
|
@@ -4590,14 +4652,14 @@ var LocalBackend = class {
|
|
|
4590
4652
|
};
|
|
4591
4653
|
}
|
|
4592
4654
|
async stopSession(_session) {
|
|
4593
|
-
return (0,
|
|
4655
|
+
return (0, import_types14.Ok)(void 0);
|
|
4594
4656
|
}
|
|
4595
4657
|
async healthCheck() {
|
|
4596
4658
|
try {
|
|
4597
4659
|
await this.client.models.list();
|
|
4598
|
-
return (0,
|
|
4660
|
+
return (0, import_types14.Ok)(void 0);
|
|
4599
4661
|
} catch (err) {
|
|
4600
|
-
return (0,
|
|
4662
|
+
return (0, import_types14.Err)({
|
|
4601
4663
|
category: "response_error",
|
|
4602
4664
|
message: err instanceof Error ? err.message : "Local backend health check failed"
|
|
4603
4665
|
});
|
|
@@ -4607,7 +4669,7 @@ var LocalBackend = class {
|
|
|
4607
4669
|
|
|
4608
4670
|
// src/agent/backends/pi.ts
|
|
4609
4671
|
var import_node_crypto4 = require("crypto");
|
|
4610
|
-
var
|
|
4672
|
+
var import_types15 = require("@harness-engineering/types");
|
|
4611
4673
|
var SILENT_EVENTS = /* @__PURE__ */ new Set([
|
|
4612
4674
|
"turn_end",
|
|
4613
4675
|
"message_start",
|
|
@@ -4724,7 +4786,7 @@ var PiBackend = class {
|
|
|
4724
4786
|
if (this.config.getModel) {
|
|
4725
4787
|
const candidate = this.config.getModel();
|
|
4726
4788
|
if (candidate === null) {
|
|
4727
|
-
return (0,
|
|
4789
|
+
return (0, import_types15.Err)({
|
|
4728
4790
|
category: "agent_not_found",
|
|
4729
4791
|
message: "No local model available; check dashboard for details."
|
|
4730
4792
|
});
|
|
@@ -4742,7 +4804,6 @@ var PiBackend = class {
|
|
|
4742
4804
|
const { session: piSession } = await piSdk.createAgentSession({
|
|
4743
4805
|
cwd: params.workspacePath,
|
|
4744
4806
|
...model !== void 0 && { model },
|
|
4745
|
-
tools: piSdk.codingTools,
|
|
4746
4807
|
sessionManager: piSdk.SessionManager.inMemory()
|
|
4747
4808
|
});
|
|
4748
4809
|
const session = {
|
|
@@ -4753,9 +4814,9 @@ var PiBackend = class {
|
|
|
4753
4814
|
piSession,
|
|
4754
4815
|
unsubscribe: null
|
|
4755
4816
|
};
|
|
4756
|
-
return (0,
|
|
4817
|
+
return (0, import_types15.Ok)(session);
|
|
4757
4818
|
} catch (err) {
|
|
4758
|
-
return (0,
|
|
4819
|
+
return (0, import_types15.Err)({
|
|
4759
4820
|
category: "response_error",
|
|
4760
4821
|
message: `Failed to create pi session: ${err instanceof Error ? err.message : String(err)}`
|
|
4761
4822
|
});
|
|
@@ -4885,14 +4946,14 @@ var PiBackend = class {
|
|
|
4885
4946
|
await piSession.abort();
|
|
4886
4947
|
} catch {
|
|
4887
4948
|
}
|
|
4888
|
-
return (0,
|
|
4949
|
+
return (0, import_types15.Ok)(void 0);
|
|
4889
4950
|
}
|
|
4890
4951
|
async healthCheck() {
|
|
4891
4952
|
try {
|
|
4892
4953
|
await import("@mariozechner/pi-coding-agent");
|
|
4893
|
-
return (0,
|
|
4954
|
+
return (0, import_types15.Ok)(void 0);
|
|
4894
4955
|
} catch (err) {
|
|
4895
|
-
return (0,
|
|
4956
|
+
return (0, import_types15.Err)({
|
|
4896
4957
|
category: "agent_not_found",
|
|
4897
4958
|
message: `Pi SDK not available: ${err instanceof Error ? err.message : String(err)}`
|
|
4898
4959
|
});
|
|
@@ -4955,7 +5016,7 @@ function createBackend(def) {
|
|
|
4955
5016
|
}
|
|
4956
5017
|
|
|
4957
5018
|
// src/agent/backends/container.ts
|
|
4958
|
-
var
|
|
5019
|
+
var import_types16 = require("@harness-engineering/types");
|
|
4959
5020
|
function toAgentError(message, details) {
|
|
4960
5021
|
return { category: "response_error", message, details };
|
|
4961
5022
|
}
|
|
@@ -4986,7 +5047,7 @@ var ContainerBackend = class {
|
|
|
4986
5047
|
}
|
|
4987
5048
|
const result = await this.secretBackend.resolveSecrets(this.secretKeys);
|
|
4988
5049
|
if (!result.ok) {
|
|
4989
|
-
return (0,
|
|
5050
|
+
return (0, import_types16.Err)(toAgentError(`Secret resolution failed: ${result.error.message}`, result.error));
|
|
4990
5051
|
}
|
|
4991
5052
|
return { ok: true, value: result.value };
|
|
4992
5053
|
}
|
|
@@ -5011,7 +5072,7 @@ var ContainerBackend = class {
|
|
|
5011
5072
|
const createOpts = this.buildCreateOpts(params, envResult.value);
|
|
5012
5073
|
const containerResult = await this.runtime.createContainer(createOpts);
|
|
5013
5074
|
if (!containerResult.ok) {
|
|
5014
|
-
return (0,
|
|
5075
|
+
return (0, import_types16.Err)(
|
|
5015
5076
|
toAgentError(
|
|
5016
5077
|
`Container creation failed: ${containerResult.error.message}`,
|
|
5017
5078
|
containerResult.error
|
|
@@ -5036,7 +5097,7 @@ var ContainerBackend = class {
|
|
|
5036
5097
|
this.containerHandles.delete(session.sessionId);
|
|
5037
5098
|
const removeResult = await this.runtime.removeContainer(handle);
|
|
5038
5099
|
if (!removeResult.ok) {
|
|
5039
|
-
return (0,
|
|
5100
|
+
return (0, import_types16.Err)(
|
|
5040
5101
|
toAgentError(
|
|
5041
5102
|
`Container removal failed: ${removeResult.error.message}`,
|
|
5042
5103
|
removeResult.error
|
|
@@ -5049,7 +5110,7 @@ var ContainerBackend = class {
|
|
|
5049
5110
|
async healthCheck() {
|
|
5050
5111
|
const runtimeResult = await this.runtime.healthCheck();
|
|
5051
5112
|
if (!runtimeResult.ok) {
|
|
5052
|
-
return (0,
|
|
5113
|
+
return (0, import_types16.Err)({
|
|
5053
5114
|
category: "agent_not_found",
|
|
5054
5115
|
message: `Container runtime unhealthy: ${runtimeResult.error.message}`,
|
|
5055
5116
|
details: runtimeResult.error
|
|
@@ -5061,7 +5122,7 @@ var ContainerBackend = class {
|
|
|
5061
5122
|
|
|
5062
5123
|
// src/agent/runtime/docker.ts
|
|
5063
5124
|
var import_node_child_process5 = require("child_process");
|
|
5064
|
-
var
|
|
5125
|
+
var import_types17 = require("@harness-engineering/types");
|
|
5065
5126
|
function dockerExec(args) {
|
|
5066
5127
|
return new Promise((resolve6, reject) => {
|
|
5067
5128
|
(0, import_node_child_process5.execFile)("docker", args, (error, stdout) => {
|
|
@@ -5094,9 +5155,9 @@ var DockerRuntime = class {
|
|
|
5094
5155
|
args.push(opts.image);
|
|
5095
5156
|
args.push("sleep", "infinity");
|
|
5096
5157
|
const containerId = await dockerExec(args);
|
|
5097
|
-
return (0,
|
|
5158
|
+
return (0, import_types17.Ok)({ containerId, runtime: this.name });
|
|
5098
5159
|
} catch (error) {
|
|
5099
|
-
return (0,
|
|
5160
|
+
return (0, import_types17.Err)({
|
|
5100
5161
|
category: "container_create_failed",
|
|
5101
5162
|
message: `Failed to create container: ${error instanceof Error ? error.message : String(error)}`,
|
|
5102
5163
|
details: error
|
|
@@ -5140,9 +5201,9 @@ var DockerRuntime = class {
|
|
|
5140
5201
|
async removeContainer(handle) {
|
|
5141
5202
|
try {
|
|
5142
5203
|
await dockerExec(["rm", "-f", handle.containerId]);
|
|
5143
|
-
return (0,
|
|
5204
|
+
return (0, import_types17.Ok)(void 0);
|
|
5144
5205
|
} catch (error) {
|
|
5145
|
-
return (0,
|
|
5206
|
+
return (0, import_types17.Err)({
|
|
5146
5207
|
category: "container_remove_failed",
|
|
5147
5208
|
message: `Failed to remove container: ${error instanceof Error ? error.message : String(error)}`,
|
|
5148
5209
|
details: error
|
|
@@ -5152,9 +5213,9 @@ var DockerRuntime = class {
|
|
|
5152
5213
|
async healthCheck() {
|
|
5153
5214
|
try {
|
|
5154
5215
|
await dockerExec(["info", "--format", "{{.ServerVersion}}"]);
|
|
5155
|
-
return (0,
|
|
5216
|
+
return (0, import_types17.Ok)(void 0);
|
|
5156
5217
|
} catch (error) {
|
|
5157
|
-
return (0,
|
|
5218
|
+
return (0, import_types17.Err)({
|
|
5158
5219
|
category: "runtime_not_found",
|
|
5159
5220
|
message: `Docker is not available: ${error instanceof Error ? error.message : String(error)}`,
|
|
5160
5221
|
details: error
|
|
@@ -5164,7 +5225,7 @@ var DockerRuntime = class {
|
|
|
5164
5225
|
};
|
|
5165
5226
|
|
|
5166
5227
|
// src/agent/secrets/env.ts
|
|
5167
|
-
var
|
|
5228
|
+
var import_types18 = require("@harness-engineering/types");
|
|
5168
5229
|
var EnvSecretBackend = class {
|
|
5169
5230
|
name = "env";
|
|
5170
5231
|
async resolveSecrets(keys) {
|
|
@@ -5172,7 +5233,7 @@ var EnvSecretBackend = class {
|
|
|
5172
5233
|
for (const key of keys) {
|
|
5173
5234
|
const value = process.env[key];
|
|
5174
5235
|
if (value === void 0) {
|
|
5175
|
-
return (0,
|
|
5236
|
+
return (0, import_types18.Err)({
|
|
5176
5237
|
category: "secret_not_found",
|
|
5177
5238
|
message: `Environment variable '${key}' is not set`,
|
|
5178
5239
|
key
|
|
@@ -5180,16 +5241,16 @@ var EnvSecretBackend = class {
|
|
|
5180
5241
|
}
|
|
5181
5242
|
secrets[key] = value;
|
|
5182
5243
|
}
|
|
5183
|
-
return (0,
|
|
5244
|
+
return (0, import_types18.Ok)(secrets);
|
|
5184
5245
|
}
|
|
5185
5246
|
async healthCheck() {
|
|
5186
|
-
return (0,
|
|
5247
|
+
return (0, import_types18.Ok)(void 0);
|
|
5187
5248
|
}
|
|
5188
5249
|
};
|
|
5189
5250
|
|
|
5190
5251
|
// src/agent/secrets/onepassword.ts
|
|
5191
5252
|
var import_node_child_process6 = require("child_process");
|
|
5192
|
-
var
|
|
5253
|
+
var import_types19 = require("@harness-engineering/types");
|
|
5193
5254
|
function opExec(args) {
|
|
5194
5255
|
return new Promise((resolve6, reject) => {
|
|
5195
5256
|
(0, import_node_child_process6.execFile)("op", args, (error, stdout) => {
|
|
@@ -5214,21 +5275,21 @@ var OnePasswordSecretBackend = class {
|
|
|
5214
5275
|
const value = await opExec(["read", `op://${this.vault}/${key}/password`]);
|
|
5215
5276
|
secrets[key] = value;
|
|
5216
5277
|
} catch (error) {
|
|
5217
|
-
return (0,
|
|
5278
|
+
return (0, import_types19.Err)({
|
|
5218
5279
|
category: "access_denied",
|
|
5219
5280
|
message: `Failed to read secret '${key}' from 1Password: ${error instanceof Error ? error.message : String(error)}`,
|
|
5220
5281
|
key
|
|
5221
5282
|
});
|
|
5222
5283
|
}
|
|
5223
5284
|
}
|
|
5224
|
-
return (0,
|
|
5285
|
+
return (0, import_types19.Ok)(secrets);
|
|
5225
5286
|
}
|
|
5226
5287
|
async healthCheck() {
|
|
5227
5288
|
try {
|
|
5228
5289
|
await opExec(["--version"]);
|
|
5229
|
-
return (0,
|
|
5290
|
+
return (0, import_types19.Ok)(void 0);
|
|
5230
5291
|
} catch (error) {
|
|
5231
|
-
return (0,
|
|
5292
|
+
return (0, import_types19.Err)({
|
|
5232
5293
|
category: "provider_unavailable",
|
|
5233
5294
|
message: `1Password CLI is not available: ${error instanceof Error ? error.message : String(error)}`
|
|
5234
5295
|
});
|
|
@@ -5238,7 +5299,7 @@ var OnePasswordSecretBackend = class {
|
|
|
5238
5299
|
|
|
5239
5300
|
// src/agent/secrets/vault.ts
|
|
5240
5301
|
var import_node_child_process7 = require("child_process");
|
|
5241
|
-
var
|
|
5302
|
+
var import_types20 = require("@harness-engineering/types");
|
|
5242
5303
|
function vaultExec(args, env) {
|
|
5243
5304
|
return new Promise((resolve6, reject) => {
|
|
5244
5305
|
(0, import_node_child_process7.execFile)("vault", args, { env: { ...process.env, ...env } }, (error, stdout) => {
|
|
@@ -5269,11 +5330,11 @@ var VaultSecretBackend = class {
|
|
|
5269
5330
|
} catch (error) {
|
|
5270
5331
|
const msg = error instanceof Error ? error.message : String(error);
|
|
5271
5332
|
const category = error instanceof SyntaxError ? "access_denied" : "access_denied";
|
|
5272
|
-
return (0,
|
|
5333
|
+
return (0, import_types20.Err)({ category, message: `Failed to read from Vault: ${msg}` });
|
|
5273
5334
|
}
|
|
5274
5335
|
const missing = keys.find((k) => !(k in data));
|
|
5275
5336
|
if (missing) {
|
|
5276
|
-
return (0,
|
|
5337
|
+
return (0, import_types20.Err)({
|
|
5277
5338
|
category: "secret_not_found",
|
|
5278
5339
|
message: `Secret key '${missing}' not found in Vault path '${this.path}'`,
|
|
5279
5340
|
key: missing
|
|
@@ -5281,14 +5342,14 @@ var VaultSecretBackend = class {
|
|
|
5281
5342
|
}
|
|
5282
5343
|
const secrets = {};
|
|
5283
5344
|
for (const key of keys) secrets[key] = data[key];
|
|
5284
|
-
return (0,
|
|
5345
|
+
return (0, import_types20.Ok)(secrets);
|
|
5285
5346
|
}
|
|
5286
5347
|
async healthCheck() {
|
|
5287
5348
|
try {
|
|
5288
5349
|
await vaultExec(["version"]);
|
|
5289
|
-
return (0,
|
|
5350
|
+
return (0, import_types20.Ok)(void 0);
|
|
5290
5351
|
} catch (error) {
|
|
5291
|
-
return (0,
|
|
5352
|
+
return (0, import_types20.Err)({
|
|
5292
5353
|
category: "provider_unavailable",
|
|
5293
5354
|
message: `Vault CLI is not available: ${error instanceof Error ? error.message : String(error)}`
|
|
5294
5355
|
});
|
|
@@ -5403,6 +5464,10 @@ var OrchestratorBackendFactory = class {
|
|
|
5403
5464
|
}
|
|
5404
5465
|
};
|
|
5405
5466
|
|
|
5467
|
+
// src/agent/intelligence-factory.ts
|
|
5468
|
+
var import_intelligence3 = require("@harness-engineering/intelligence");
|
|
5469
|
+
var import_graph = require("@harness-engineering/graph");
|
|
5470
|
+
|
|
5406
5471
|
// src/agent/analysis-provider-factory.ts
|
|
5407
5472
|
var import_intelligence2 = require("@harness-engineering/intelligence");
|
|
5408
5473
|
function buildAnalysisProvider(args) {
|
|
@@ -5500,9 +5565,110 @@ function buildClaudeCliProvider(def, args, layerModel) {
|
|
|
5500
5565
|
});
|
|
5501
5566
|
}
|
|
5502
5567
|
|
|
5568
|
+
// src/agent/intelligence-factory.ts
|
|
5569
|
+
function buildIntelligencePipeline(deps) {
|
|
5570
|
+
const { config } = deps;
|
|
5571
|
+
const intel = config.intelligence;
|
|
5572
|
+
if (!intel?.enabled) return null;
|
|
5573
|
+
const selProvider = buildAnalysisProviderForLayer("sel", deps);
|
|
5574
|
+
if (!selProvider) return null;
|
|
5575
|
+
const routing = config.agent.routing;
|
|
5576
|
+
const peslName = routing?.intelligence?.pesl;
|
|
5577
|
+
const selName = routing?.intelligence?.sel ?? routing?.default;
|
|
5578
|
+
const peslProvider = peslName !== void 0 && peslName !== selName ? buildAnalysisProviderForLayer("pesl", deps) : null;
|
|
5579
|
+
const peslModel = intel.models?.pesl ?? config.agent.model;
|
|
5580
|
+
const graphStore = new import_graph.GraphStore();
|
|
5581
|
+
const pipeline = new import_intelligence3.IntelligencePipeline(selProvider, graphStore, {
|
|
5582
|
+
...peslModel !== void 0 && { peslModel },
|
|
5583
|
+
...peslProvider !== null && peslProvider !== void 0 && { peslProvider }
|
|
5584
|
+
});
|
|
5585
|
+
return { pipeline, graphStore };
|
|
5586
|
+
}
|
|
5587
|
+
function buildAnalysisProviderForLayer(layer, deps) {
|
|
5588
|
+
const { config, localResolvers, logger } = deps;
|
|
5589
|
+
const intel = config.intelligence;
|
|
5590
|
+
if (!intel?.enabled) return null;
|
|
5591
|
+
if (intel.provider) {
|
|
5592
|
+
const layerModel = layer === "sel" ? intel.models?.sel : intel.models?.pesl;
|
|
5593
|
+
return buildExplicitProvider(intel.provider, layerModel ?? config.agent.model, config);
|
|
5594
|
+
}
|
|
5595
|
+
const routed = resolveRoutedBackend(layer, config, logger);
|
|
5596
|
+
if (!routed) return null;
|
|
5597
|
+
const { name, def } = routed;
|
|
5598
|
+
const resolver = localResolvers.get(name);
|
|
5599
|
+
return buildAnalysisProvider({
|
|
5600
|
+
def,
|
|
5601
|
+
backendName: name,
|
|
5602
|
+
layer,
|
|
5603
|
+
// Spec 2 P3-IMP-1: a single snapshot read feeds the factory's
|
|
5604
|
+
// unavailable-warn diagnostic (Configured/Detected lists) and
|
|
5605
|
+
// collapses the two `getStatus()` calls flagged by P3-SUG-2.
|
|
5606
|
+
getResolverStatusSnapshot: () => {
|
|
5607
|
+
if (!resolver) return null;
|
|
5608
|
+
const status = resolver.getStatus();
|
|
5609
|
+
return {
|
|
5610
|
+
available: status.available,
|
|
5611
|
+
resolved: status.resolved,
|
|
5612
|
+
configured: status.configured,
|
|
5613
|
+
detected: status.detected
|
|
5614
|
+
};
|
|
5615
|
+
},
|
|
5616
|
+
intelligence: intel,
|
|
5617
|
+
logger
|
|
5618
|
+
});
|
|
5619
|
+
}
|
|
5620
|
+
function resolveRoutedBackend(layer, config, logger) {
|
|
5621
|
+
const routing = config.agent.routing;
|
|
5622
|
+
const backends = config.agent.backends;
|
|
5623
|
+
if (!routing || !backends) return null;
|
|
5624
|
+
const layerName = routing.intelligence?.[layer];
|
|
5625
|
+
const name = layerName ?? routing.default;
|
|
5626
|
+
const def = backends[name];
|
|
5627
|
+
if (!def) {
|
|
5628
|
+
logger.warn(
|
|
5629
|
+
`Intelligence pipeline: routed backend '${name}' for layer '${layer}' is not in agent.backends.`
|
|
5630
|
+
);
|
|
5631
|
+
return null;
|
|
5632
|
+
}
|
|
5633
|
+
return { name, def };
|
|
5634
|
+
}
|
|
5635
|
+
function buildExplicitProvider(provider, selModel, config) {
|
|
5636
|
+
if (provider.kind === "anthropic") {
|
|
5637
|
+
const apiKey2 = provider.apiKey ?? config.agent.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
5638
|
+
if (!apiKey2) {
|
|
5639
|
+
throw new Error("Intelligence pipeline: no Anthropic API key found.");
|
|
5640
|
+
}
|
|
5641
|
+
return new import_intelligence3.AnthropicAnalysisProvider({
|
|
5642
|
+
apiKey: apiKey2,
|
|
5643
|
+
...selModel !== void 0 && { defaultModel: selModel }
|
|
5644
|
+
});
|
|
5645
|
+
}
|
|
5646
|
+
if (provider.kind === "claude-cli") {
|
|
5647
|
+
return new import_intelligence3.ClaudeCliAnalysisProvider({
|
|
5648
|
+
command: config.agent.command,
|
|
5649
|
+
...selModel !== void 0 && { defaultModel: selModel },
|
|
5650
|
+
...config.intelligence?.requestTimeoutMs !== void 0 && {
|
|
5651
|
+
timeoutMs: config.intelligence.requestTimeoutMs
|
|
5652
|
+
}
|
|
5653
|
+
});
|
|
5654
|
+
}
|
|
5655
|
+
const apiKey = provider.apiKey ?? config.agent.apiKey ?? "ollama";
|
|
5656
|
+
const baseUrl = provider.baseUrl ?? "http://localhost:11434/v1";
|
|
5657
|
+
const intel = config.intelligence;
|
|
5658
|
+
return new import_intelligence3.OpenAICompatibleAnalysisProvider({
|
|
5659
|
+
apiKey,
|
|
5660
|
+
baseUrl,
|
|
5661
|
+
...selModel !== void 0 && { defaultModel: selModel },
|
|
5662
|
+
...intel?.requestTimeoutMs !== void 0 && { timeoutMs: intel.requestTimeoutMs },
|
|
5663
|
+
...intel?.promptSuffix !== void 0 && { promptSuffix: intel.promptSuffix },
|
|
5664
|
+
...intel?.jsonMode !== void 0 && { jsonMode: intel.jsonMode }
|
|
5665
|
+
});
|
|
5666
|
+
}
|
|
5667
|
+
|
|
5503
5668
|
// src/server/http.ts
|
|
5504
5669
|
var http = __toESM(require("http"));
|
|
5505
|
-
var
|
|
5670
|
+
var path14 = __toESM(require("path"));
|
|
5671
|
+
var import_core8 = require("@harness-engineering/core");
|
|
5506
5672
|
|
|
5507
5673
|
// src/server/websocket.ts
|
|
5508
5674
|
var import_ws = require("ws");
|
|
@@ -5930,7 +6096,7 @@ function extractChunks(event) {
|
|
|
5930
6096
|
}
|
|
5931
6097
|
|
|
5932
6098
|
// src/server/routes/analyze.ts
|
|
5933
|
-
var
|
|
6099
|
+
var import_intelligence4 = require("@harness-engineering/intelligence");
|
|
5934
6100
|
var import_zod6 = require("zod");
|
|
5935
6101
|
var AnalyzeRequestSchema = import_zod6.z.object({
|
|
5936
6102
|
title: import_zod6.z.string().min(1),
|
|
@@ -5960,7 +6126,7 @@ async function runPipeline(res, pipeline, parsed) {
|
|
|
5960
6126
|
disconnected = true;
|
|
5961
6127
|
});
|
|
5962
6128
|
emit2(res, { type: "status", text: "Converting to work item..." });
|
|
5963
|
-
const rawItem = (0,
|
|
6129
|
+
const rawItem = (0, import_intelligence4.manualToRawWorkItem)({
|
|
5964
6130
|
title: parsed.title,
|
|
5965
6131
|
description: parsed.description ?? "",
|
|
5966
6132
|
labels: parsed.labels ?? []
|
|
@@ -6003,7 +6169,7 @@ async function runPipeline(res, pipeline, parsed) {
|
|
|
6003
6169
|
}
|
|
6004
6170
|
}
|
|
6005
6171
|
if (disconnected) return;
|
|
6006
|
-
const signals = (0,
|
|
6172
|
+
const signals = (0, import_intelligence4.scoreToConcernSignals)(score);
|
|
6007
6173
|
if (signals.length > 0) {
|
|
6008
6174
|
emit2(res, { type: "signals", data: signals });
|
|
6009
6175
|
}
|
|
@@ -6042,6 +6208,7 @@ function handleAnalyzeRoute(req, res, pipeline) {
|
|
|
6042
6208
|
|
|
6043
6209
|
// src/server/routes/roadmap-actions.ts
|
|
6044
6210
|
var fs10 = __toESM(require("fs/promises"));
|
|
6211
|
+
var path10 = __toESM(require("path"));
|
|
6045
6212
|
var import_core7 = require("@harness-engineering/core");
|
|
6046
6213
|
var import_zod7 = require("zod");
|
|
6047
6214
|
var AppendRoadmapRequestSchema = import_zod7.z.object({
|
|
@@ -6069,6 +6236,48 @@ function handleRoadmapActionsRoute(req, res, roadmapPath) {
|
|
|
6069
6236
|
sendJSON(res, 503, { error: "Roadmap path not configured" });
|
|
6070
6237
|
return;
|
|
6071
6238
|
}
|
|
6239
|
+
const projectRoot = path10.dirname(path10.dirname(roadmapPath));
|
|
6240
|
+
const mode = (0, import_core7.loadProjectRoadmapMode)(projectRoot);
|
|
6241
|
+
if (mode === "file-less") {
|
|
6242
|
+
const trackerCfg = (0, import_core7.loadTrackerClientConfigFromProject)(projectRoot);
|
|
6243
|
+
if (!trackerCfg.ok) {
|
|
6244
|
+
sendJSON(res, 500, { error: trackerCfg.error.message });
|
|
6245
|
+
return;
|
|
6246
|
+
}
|
|
6247
|
+
const clientR = (0, import_core7.createTrackerClient)(trackerCfg.value);
|
|
6248
|
+
if (!clientR.ok) {
|
|
6249
|
+
sendJSON(res, 500, { error: clientR.error.message });
|
|
6250
|
+
return;
|
|
6251
|
+
}
|
|
6252
|
+
const body2 = await readBody(req);
|
|
6253
|
+
const parseResult = AppendRoadmapRequestSchema.safeParse(JSON.parse(body2));
|
|
6254
|
+
if (!parseResult.success) {
|
|
6255
|
+
sendJSON(res, 400, {
|
|
6256
|
+
error: parseResult.error.issues[0]?.message ?? "Invalid request body"
|
|
6257
|
+
});
|
|
6258
|
+
return;
|
|
6259
|
+
}
|
|
6260
|
+
const newFeature = {
|
|
6261
|
+
name: parseResult.data.title,
|
|
6262
|
+
summary: parseResult.data.enrichedSpec?.intent ?? parseResult.data.summary ?? parseResult.data.title,
|
|
6263
|
+
status: "planned"
|
|
6264
|
+
};
|
|
6265
|
+
const r = await clientR.value.create(newFeature);
|
|
6266
|
+
if (!r.ok) {
|
|
6267
|
+
if (r.error instanceof import_core7.ConflictError) {
|
|
6268
|
+
sendJSON(res, 409, (0, import_core7.makeTrackerConflictBody)(r.error));
|
|
6269
|
+
return;
|
|
6270
|
+
}
|
|
6271
|
+
sendJSON(res, 502, { error: r.error.message });
|
|
6272
|
+
return;
|
|
6273
|
+
}
|
|
6274
|
+
sendJSON(res, 201, {
|
|
6275
|
+
ok: true,
|
|
6276
|
+
featureName: r.value.name,
|
|
6277
|
+
externalId: r.value.externalId
|
|
6278
|
+
});
|
|
6279
|
+
return;
|
|
6280
|
+
}
|
|
6072
6281
|
const body = await readBody(req);
|
|
6073
6282
|
const result = AppendRoadmapRequestSchema.safeParse(JSON.parse(body));
|
|
6074
6283
|
if (!result.success) {
|
|
@@ -6321,21 +6530,21 @@ function handleMaintenanceRoute(req, res, deps) {
|
|
|
6321
6530
|
|
|
6322
6531
|
// src/server/routes/sessions.ts
|
|
6323
6532
|
var fs11 = __toESM(require("fs/promises"));
|
|
6324
|
-
var
|
|
6533
|
+
var path11 = __toESM(require("path"));
|
|
6325
6534
|
var import_zod10 = require("zod");
|
|
6326
6535
|
var SessionCreateSchema = import_zod10.z.object({
|
|
6327
6536
|
sessionId: import_zod10.z.string().min(1)
|
|
6328
6537
|
}).passthrough();
|
|
6329
6538
|
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6330
6539
|
function isSafeId(id) {
|
|
6331
|
-
return UUID_RE2.test(id) ||
|
|
6540
|
+
return UUID_RE2.test(id) || path11.basename(id) === id && !id.includes("..");
|
|
6332
6541
|
}
|
|
6333
6542
|
function jsonResponse(res, status, data) {
|
|
6334
6543
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
6335
6544
|
res.end(JSON.stringify(data));
|
|
6336
6545
|
}
|
|
6337
6546
|
function extractSessionId(url) {
|
|
6338
|
-
const segments = new URL(url, "http://localhost").pathname.split(
|
|
6547
|
+
const segments = new URL(url, "http://localhost").pathname.split(path11.posix.sep);
|
|
6339
6548
|
const id = segments.pop();
|
|
6340
6549
|
return id && id !== "sessions" ? id : null;
|
|
6341
6550
|
}
|
|
@@ -6347,7 +6556,7 @@ async function handleList(res, sessionsDir) {
|
|
|
6347
6556
|
if (!entry.isDirectory()) continue;
|
|
6348
6557
|
try {
|
|
6349
6558
|
const content = await fs11.readFile(
|
|
6350
|
-
|
|
6559
|
+
path11.join(sessionsDir, entry.name, "session.json"),
|
|
6351
6560
|
"utf-8"
|
|
6352
6561
|
);
|
|
6353
6562
|
sessions.push(JSON.parse(content));
|
|
@@ -6372,7 +6581,7 @@ async function handleGet(res, id, sessionsDir) {
|
|
|
6372
6581
|
return;
|
|
6373
6582
|
}
|
|
6374
6583
|
try {
|
|
6375
|
-
const content = await fs11.readFile(
|
|
6584
|
+
const content = await fs11.readFile(path11.join(sessionsDir, id, "session.json"), "utf-8");
|
|
6376
6585
|
jsonResponse(res, 200, JSON.parse(content));
|
|
6377
6586
|
} catch (err) {
|
|
6378
6587
|
if (err.code === "ENOENT") {
|
|
@@ -6395,9 +6604,9 @@ async function handleCreate(req, res, sessionsDir) {
|
|
|
6395
6604
|
jsonResponse(res, 400, { error: "Invalid sessionId" });
|
|
6396
6605
|
return;
|
|
6397
6606
|
}
|
|
6398
|
-
const sessionDir =
|
|
6607
|
+
const sessionDir = path11.join(sessionsDir, session.sessionId);
|
|
6399
6608
|
await fs11.mkdir(sessionDir, { recursive: true });
|
|
6400
|
-
await fs11.writeFile(
|
|
6609
|
+
await fs11.writeFile(path11.join(sessionDir, "session.json"), JSON.stringify(session, null, 2));
|
|
6401
6610
|
jsonResponse(res, 200, { ok: true });
|
|
6402
6611
|
} catch {
|
|
6403
6612
|
jsonResponse(res, 500, { error: "Failed to save session" });
|
|
@@ -6412,7 +6621,7 @@ async function handleUpdate(req, res, url, sessionsDir) {
|
|
|
6412
6621
|
}
|
|
6413
6622
|
const body = await readBody(req);
|
|
6414
6623
|
const updates = import_zod10.z.record(import_zod10.z.unknown()).parse(JSON.parse(body));
|
|
6415
|
-
const sessionFilePath =
|
|
6624
|
+
const sessionFilePath = path11.join(sessionsDir, id, "session.json");
|
|
6416
6625
|
const current = JSON.parse(await fs11.readFile(sessionFilePath, "utf-8"));
|
|
6417
6626
|
await fs11.writeFile(sessionFilePath, JSON.stringify({ ...current, ...updates }, null, 2));
|
|
6418
6627
|
jsonResponse(res, 200, { ok: true });
|
|
@@ -6427,7 +6636,7 @@ async function handleDelete(res, url, sessionsDir) {
|
|
|
6427
6636
|
jsonResponse(res, 400, { error: "Missing or invalid sessionId" });
|
|
6428
6637
|
return;
|
|
6429
6638
|
}
|
|
6430
|
-
await fs11.rm(
|
|
6639
|
+
await fs11.rm(path11.join(sessionsDir, id), { recursive: true, force: true });
|
|
6431
6640
|
jsonResponse(res, 200, { ok: true });
|
|
6432
6641
|
} catch {
|
|
6433
6642
|
jsonResponse(res, 500, { error: "Failed to delete session" });
|
|
@@ -6568,7 +6777,7 @@ function handleLocalModelsRoute(req, res, getStatuses) {
|
|
|
6568
6777
|
|
|
6569
6778
|
// src/server/static.ts
|
|
6570
6779
|
var fs12 = __toESM(require("fs"));
|
|
6571
|
-
var
|
|
6780
|
+
var path12 = __toESM(require("path"));
|
|
6572
6781
|
var MIME_TYPES = {
|
|
6573
6782
|
".html": "text/html; charset=utf-8",
|
|
6574
6783
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -6588,26 +6797,26 @@ var MIME_TYPES = {
|
|
|
6588
6797
|
function handleStaticFile(req, res, dashboardDir) {
|
|
6589
6798
|
const { method, url } = req;
|
|
6590
6799
|
if (method !== "GET") return false;
|
|
6591
|
-
const apiPrefix =
|
|
6592
|
-
const wsPath =
|
|
6800
|
+
const apiPrefix = path12.posix.join(path12.posix.sep, "api", path12.posix.sep);
|
|
6801
|
+
const wsPath = path12.posix.join(path12.posix.sep, "ws");
|
|
6593
6802
|
if (url?.startsWith(apiPrefix) || url === wsPath) return false;
|
|
6594
6803
|
const urlPath = new URL(url ?? "/", "http://localhost").pathname;
|
|
6595
|
-
const requestedPath =
|
|
6596
|
-
const resolved =
|
|
6597
|
-
if (!resolved.startsWith(
|
|
6598
|
-
return serveFile(
|
|
6804
|
+
const requestedPath = path12.join(dashboardDir, urlPath === "/" ? "index.html" : urlPath);
|
|
6805
|
+
const resolved = path12.resolve(requestedPath);
|
|
6806
|
+
if (!resolved.startsWith(path12.resolve(dashboardDir))) {
|
|
6807
|
+
return serveFile(path12.join(dashboardDir, "index.html"), res);
|
|
6599
6808
|
}
|
|
6600
6809
|
if (fs12.existsSync(resolved) && fs12.statSync(resolved).isFile()) {
|
|
6601
6810
|
return serveFile(resolved, res);
|
|
6602
6811
|
}
|
|
6603
|
-
const indexPath =
|
|
6812
|
+
const indexPath = path12.join(dashboardDir, "index.html");
|
|
6604
6813
|
if (fs12.existsSync(indexPath)) {
|
|
6605
6814
|
return serveFile(indexPath, res);
|
|
6606
6815
|
}
|
|
6607
6816
|
return false;
|
|
6608
6817
|
}
|
|
6609
6818
|
function serveFile(filePath, res) {
|
|
6610
|
-
const ext =
|
|
6819
|
+
const ext = path12.extname(filePath).toLowerCase();
|
|
6611
6820
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
6612
6821
|
try {
|
|
6613
6822
|
const content = fs12.readFileSync(filePath);
|
|
@@ -6621,7 +6830,7 @@ function serveFile(filePath, res) {
|
|
|
6621
6830
|
|
|
6622
6831
|
// src/server/plan-watcher.ts
|
|
6623
6832
|
var fs13 = __toESM(require("fs"));
|
|
6624
|
-
var
|
|
6833
|
+
var path13 = __toESM(require("path"));
|
|
6625
6834
|
var PlanWatcher = class {
|
|
6626
6835
|
plansDir;
|
|
6627
6836
|
queue;
|
|
@@ -6638,7 +6847,7 @@ var PlanWatcher = class {
|
|
|
6638
6847
|
fs13.mkdirSync(this.plansDir, { recursive: true });
|
|
6639
6848
|
this.watcher = fs13.watch(this.plansDir, (eventType, filename) => {
|
|
6640
6849
|
if (eventType === "rename" && filename && filename.endsWith(".md")) {
|
|
6641
|
-
const filePath =
|
|
6850
|
+
const filePath = path13.join(this.plansDir, filename);
|
|
6642
6851
|
if (fs13.existsSync(filePath)) {
|
|
6643
6852
|
void this.handleNewPlan(filename);
|
|
6644
6853
|
}
|
|
@@ -6725,6 +6934,7 @@ var OrchestratorServer = class {
|
|
|
6725
6934
|
planWatcher = null;
|
|
6726
6935
|
stateChangeListener;
|
|
6727
6936
|
agentEventListener;
|
|
6937
|
+
apiRoutes;
|
|
6728
6938
|
constructor(orchestrator, port, deps) {
|
|
6729
6939
|
this.orchestrator = orchestrator;
|
|
6730
6940
|
this.port = port;
|
|
@@ -6734,18 +6944,19 @@ var OrchestratorServer = class {
|
|
|
6734
6944
|
this.httpServer,
|
|
6735
6945
|
() => this.orchestrator.getSnapshot()
|
|
6736
6946
|
);
|
|
6947
|
+
this.apiRoutes = this.buildApiRoutes();
|
|
6737
6948
|
this.wireEvents();
|
|
6738
6949
|
}
|
|
6739
6950
|
initDependencies(deps) {
|
|
6740
6951
|
this.interactionQueue = deps?.interactionQueue;
|
|
6741
|
-
this.plansDir = deps?.plansDir ??
|
|
6742
|
-
this.dashboardDir = deps?.dashboardDir ??
|
|
6952
|
+
this.plansDir = deps?.plansDir ?? path14.resolve("docs", "plans");
|
|
6953
|
+
this.dashboardDir = deps?.dashboardDir ?? path14.resolve("packages", "dashboard", "dist", "client");
|
|
6743
6954
|
this.claudeCommand = deps?.claudeCommand ?? "claude";
|
|
6744
6955
|
this.pipeline = deps?.pipeline ?? null;
|
|
6745
6956
|
this.analysisArchive = deps?.analysisArchive;
|
|
6746
6957
|
this.roadmapPath = deps?.roadmapPath ?? null;
|
|
6747
6958
|
this.dispatchAdHoc = deps?.dispatchAdHoc ?? null;
|
|
6748
|
-
this.sessionsDir = deps?.sessionsDir ??
|
|
6959
|
+
this.sessionsDir = deps?.sessionsDir ?? path14.resolve(".harness", "sessions");
|
|
6749
6960
|
this.maintenanceDeps = deps?.maintenanceDeps ?? null;
|
|
6750
6961
|
this.getLocalModelStatus = deps?.getLocalModelStatus ?? null;
|
|
6751
6962
|
this.getLocalModelStatuses = deps?.getLocalModelStatuses ?? null;
|
|
@@ -6769,8 +6980,8 @@ var OrchestratorServer = class {
|
|
|
6769
6980
|
}
|
|
6770
6981
|
/**
|
|
6771
6982
|
* Broadcast a maintenance event to all WebSocket clients.
|
|
6772
|
-
* @param type - One of 'maintenance:started', 'maintenance:completed', 'maintenance:error'
|
|
6773
|
-
* @param data - Event payload (task info, run result, or
|
|
6983
|
+
* @param type - One of 'maintenance:started', 'maintenance:completed', 'maintenance:error', 'maintenance:baseref_fallback'
|
|
6984
|
+
* @param data - Event payload (task info, run result, error details, or baseref-fallback diagnostic)
|
|
6774
6985
|
*/
|
|
6775
6986
|
broadcastMaintenance(type, data) {
|
|
6776
6987
|
this.broadcaster.broadcast(type, data);
|
|
@@ -6855,44 +7066,37 @@ var OrchestratorServer = class {
|
|
|
6855
7066
|
);
|
|
6856
7067
|
return false;
|
|
6857
7068
|
}
|
|
7069
|
+
/**
|
|
7070
|
+
* Build the ordered API route table. Each entry is invoked in order and
|
|
7071
|
+
* returns true when it has handled the request. Closures capture `this`,
|
|
7072
|
+
* so handlers re-read mutable deps (pipeline, recorder, maintenanceDeps)
|
|
7073
|
+
* on every request — setters like setPipeline() take effect immediately.
|
|
7074
|
+
*
|
|
7075
|
+
* Adding a new route is a one-place change: append an entry here.
|
|
7076
|
+
*/
|
|
7077
|
+
buildApiRoutes() {
|
|
7078
|
+
return [
|
|
7079
|
+
(req, res) => !!this.interactionQueue && handleInteractionsRoute(req, res, this.interactionQueue),
|
|
7080
|
+
(req, res) => handlePlansRoute(req, res, this.plansDir),
|
|
7081
|
+
(req, res) => handleAnalyzeRoute(req, res, this.pipeline),
|
|
7082
|
+
(req, res) => handleAnalysesRoute(req, res, this.analysisArchive),
|
|
7083
|
+
(req, res) => handleRoadmapActionsRoute(req, res, this.roadmapPath),
|
|
7084
|
+
(req, res) => handleDispatchActionsRoute(req, res, this.dispatchAdHoc),
|
|
7085
|
+
(req, res) => handleLocalModelRoute(req, res, this.getLocalModelStatus),
|
|
7086
|
+
// Local-models multi-status route (Spec 2 SC38)
|
|
7087
|
+
(req, res) => handleLocalModelsRoute(req, res, this.getLocalModelStatuses),
|
|
7088
|
+
(req, res) => handleMaintenanceRoute(req, res, this.maintenanceDeps),
|
|
7089
|
+
(req, res) => !!this.recorder && handleStreamsRoute(req, res, this.recorder),
|
|
7090
|
+
(req, res) => handleSessionsRoute(req, res, this.sessionsDir),
|
|
7091
|
+
// Chat proxy route (spawns Claude Code CLI — no API key required)
|
|
7092
|
+
(req, res) => handleChatProxyRoute(req, res, this.claudeCommand)
|
|
7093
|
+
];
|
|
7094
|
+
}
|
|
6858
7095
|
/** Dispatch to API route handlers. Returns true if a route matched. */
|
|
6859
7096
|
handleApiRoutes(req, res) {
|
|
6860
7097
|
if (!this.checkAuth(req, res)) return true;
|
|
6861
|
-
|
|
6862
|
-
return true;
|
|
6863
|
-
}
|
|
6864
|
-
if (handlePlansRoute(req, res, this.plansDir)) {
|
|
6865
|
-
return true;
|
|
6866
|
-
}
|
|
6867
|
-
if (handleAnalyzeRoute(req, res, this.pipeline)) {
|
|
6868
|
-
return true;
|
|
6869
|
-
}
|
|
6870
|
-
if (handleAnalysesRoute(req, res, this.analysisArchive)) {
|
|
6871
|
-
return true;
|
|
6872
|
-
}
|
|
6873
|
-
if (handleRoadmapActionsRoute(req, res, this.roadmapPath)) {
|
|
6874
|
-
return true;
|
|
6875
|
-
}
|
|
6876
|
-
if (handleDispatchActionsRoute(req, res, this.dispatchAdHoc)) {
|
|
6877
|
-
return true;
|
|
6878
|
-
}
|
|
6879
|
-
if (handleLocalModelRoute(req, res, this.getLocalModelStatus)) {
|
|
6880
|
-
return true;
|
|
6881
|
-
}
|
|
6882
|
-
if (handleLocalModelsRoute(req, res, this.getLocalModelStatuses)) {
|
|
6883
|
-
return true;
|
|
6884
|
-
}
|
|
6885
|
-
if (handleMaintenanceRoute(req, res, this.maintenanceDeps)) {
|
|
6886
|
-
return true;
|
|
6887
|
-
}
|
|
6888
|
-
if (this.recorder && handleStreamsRoute(req, res, this.recorder)) {
|
|
6889
|
-
return true;
|
|
6890
|
-
}
|
|
6891
|
-
if (handleSessionsRoute(req, res, this.sessionsDir)) {
|
|
6892
|
-
return true;
|
|
6893
|
-
}
|
|
6894
|
-
if (handleChatProxyRoute(req, res, this.claudeCommand)) {
|
|
6895
|
-
return true;
|
|
7098
|
+
for (const route of this.apiRoutes) {
|
|
7099
|
+
if (route(req, res)) return true;
|
|
6896
7100
|
}
|
|
6897
7101
|
return false;
|
|
6898
7102
|
}
|
|
@@ -6900,6 +7104,7 @@ var OrchestratorServer = class {
|
|
|
6900
7104
|
return this.broadcaster.clientCount;
|
|
6901
7105
|
}
|
|
6902
7106
|
async start() {
|
|
7107
|
+
(0, import_core8.assertPortUsable)(this.port, "orchestrator");
|
|
6903
7108
|
if (this.interactionQueue) {
|
|
6904
7109
|
this.planWatcher = new PlanWatcher(this.plansDir, this.interactionQueue);
|
|
6905
7110
|
this.planWatcher.start();
|
|
@@ -6964,7 +7169,7 @@ var StructuredLogger = class {
|
|
|
6964
7169
|
// src/workspace/config-scanner.ts
|
|
6965
7170
|
var import_node_fs = require("fs");
|
|
6966
7171
|
var import_node_path = require("path");
|
|
6967
|
-
var
|
|
7172
|
+
var import_core9 = require("@harness-engineering/core");
|
|
6968
7173
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".gemini/settings.json", "skill.yaml"];
|
|
6969
7174
|
var BLOCKING_INJECTION_PREFIXES = ["INJ-UNI-", "INJ-REROL-"];
|
|
6970
7175
|
var DOWNGRADED_SECURITY_RULES = /* @__PURE__ */ new Set(["SEC-AGT-006"]);
|
|
@@ -6986,25 +7191,25 @@ async function scanSingleFile(filePath, targetDir, scanner) {
|
|
|
6986
7191
|
} catch {
|
|
6987
7192
|
return null;
|
|
6988
7193
|
}
|
|
6989
|
-
const injectionFindings = (0,
|
|
6990
|
-
const findings = (0,
|
|
7194
|
+
const injectionFindings = (0, import_core9.scanForInjection)(content);
|
|
7195
|
+
const findings = (0, import_core9.mapInjectionFindings)(injectionFindings);
|
|
6991
7196
|
const secFindings = await scanner.scanFile(filePath);
|
|
6992
|
-
findings.push(...(0,
|
|
7197
|
+
findings.push(...(0, import_core9.mapSecurityFindings)(secFindings, findings));
|
|
6993
7198
|
const adjusted = adjustFindingSeverity(findings);
|
|
6994
7199
|
return {
|
|
6995
7200
|
file: (0, import_node_path.relative)(targetDir, filePath).replaceAll("\\", "/"),
|
|
6996
7201
|
findings: adjusted,
|
|
6997
|
-
overallSeverity: (0,
|
|
7202
|
+
overallSeverity: (0, import_core9.computeOverallSeverity)(adjusted)
|
|
6998
7203
|
};
|
|
6999
7204
|
}
|
|
7000
7205
|
async function scanWorkspaceConfig(workspacePath) {
|
|
7001
|
-
const scanner = new
|
|
7206
|
+
const scanner = new import_core9.SecurityScanner((0, import_core9.parseSecurityConfig)({}));
|
|
7002
7207
|
const results = [];
|
|
7003
7208
|
for (const configFile of CONFIG_FILES) {
|
|
7004
7209
|
const result = await scanSingleFile((0, import_node_path.join)(workspacePath, configFile), workspacePath, scanner);
|
|
7005
7210
|
if (result) results.push(result);
|
|
7006
7211
|
}
|
|
7007
|
-
return { exitCode: (0,
|
|
7212
|
+
return { exitCode: (0, import_core9.computeScanExitCode)(results), results };
|
|
7008
7213
|
}
|
|
7009
7214
|
|
|
7010
7215
|
// src/maintenance/task-registry.ts
|
|
@@ -7179,6 +7384,14 @@ var BUILT_IN_TASKS = [
|
|
|
7179
7384
|
schedule: "0 7 * * *",
|
|
7180
7385
|
branch: null,
|
|
7181
7386
|
checkCommand: ["perf", "baselines", "update"]
|
|
7387
|
+
},
|
|
7388
|
+
{
|
|
7389
|
+
id: "main-sync",
|
|
7390
|
+
type: "housekeeping",
|
|
7391
|
+
description: "Fast-forward local default branch from origin",
|
|
7392
|
+
schedule: "*/15 * * * *",
|
|
7393
|
+
branch: null,
|
|
7394
|
+
checkCommand: ["harness", "sync-main", "--json"]
|
|
7182
7395
|
}
|
|
7183
7396
|
];
|
|
7184
7397
|
|
|
@@ -7249,7 +7462,7 @@ function cronMatchesNow(expression, now) {
|
|
|
7249
7462
|
// src/maintenance/scheduler.ts
|
|
7250
7463
|
var MaintenanceScheduler = class {
|
|
7251
7464
|
config;
|
|
7252
|
-
|
|
7465
|
+
leaderElector;
|
|
7253
7466
|
logger;
|
|
7254
7467
|
onTaskDue;
|
|
7255
7468
|
historyProvider;
|
|
@@ -7264,7 +7477,7 @@ var MaintenanceScheduler = class {
|
|
|
7264
7477
|
activeRun = null;
|
|
7265
7478
|
constructor(options) {
|
|
7266
7479
|
this.config = options.config;
|
|
7267
|
-
this.
|
|
7480
|
+
this.leaderElector = options.leaderElector;
|
|
7268
7481
|
this.logger = options.logger;
|
|
7269
7482
|
this.historyProvider = options.historyProvider ?? null;
|
|
7270
7483
|
this.onTaskDue = options.onTaskDue;
|
|
@@ -7339,12 +7552,12 @@ var MaintenanceScheduler = class {
|
|
|
7339
7552
|
await this.processQueue(queue, epochMinute);
|
|
7340
7553
|
}
|
|
7341
7554
|
/**
|
|
7342
|
-
* Attempt to claim leadership via
|
|
7555
|
+
* Attempt to claim leadership via the configured LeaderElector.
|
|
7343
7556
|
* Returns true if this instance is the leader.
|
|
7344
7557
|
*/
|
|
7345
7558
|
async attemptLeaderClaim(evalTime) {
|
|
7346
7559
|
try {
|
|
7347
|
-
const result = await this.
|
|
7560
|
+
const result = await this.leaderElector.electLeader();
|
|
7348
7561
|
if (!result.ok) {
|
|
7349
7562
|
this.isLeader = false;
|
|
7350
7563
|
this.logger.warn("Maintenance leader claim failed", { error: result.error?.message });
|
|
@@ -7423,6 +7636,7 @@ var MaintenanceScheduler = class {
|
|
|
7423
7636
|
const history = this.historyProvider ? this.historyProvider.getHistory(200, 0) : this.internalHistory;
|
|
7424
7637
|
const schedule = this.resolvedTasks.map((task) => ({
|
|
7425
7638
|
taskId: task.id,
|
|
7639
|
+
type: task.type,
|
|
7426
7640
|
nextRun: this.computeNextRun(task.schedule),
|
|
7427
7641
|
lastRun: history.find((r) => r.taskId === task.id) ?? null
|
|
7428
7642
|
}));
|
|
@@ -7459,9 +7673,17 @@ var MaintenanceScheduler = class {
|
|
|
7459
7673
|
}
|
|
7460
7674
|
};
|
|
7461
7675
|
|
|
7676
|
+
// src/maintenance/leader-elector.ts
|
|
7677
|
+
var import_types21 = require("@harness-engineering/types");
|
|
7678
|
+
var SingleProcessLeaderElector = class {
|
|
7679
|
+
async electLeader() {
|
|
7680
|
+
return (0, import_types21.Ok)("claimed");
|
|
7681
|
+
}
|
|
7682
|
+
};
|
|
7683
|
+
|
|
7462
7684
|
// src/maintenance/reporter.ts
|
|
7463
7685
|
var fs14 = __toESM(require("fs"));
|
|
7464
|
-
var
|
|
7686
|
+
var path15 = __toESM(require("path"));
|
|
7465
7687
|
var import_zod11 = require("zod");
|
|
7466
7688
|
var RunResultSchema = import_zod11.z.object({
|
|
7467
7689
|
taskId: import_zod11.z.string(),
|
|
@@ -7497,7 +7719,7 @@ var MaintenanceReporter = class {
|
|
|
7497
7719
|
async load() {
|
|
7498
7720
|
try {
|
|
7499
7721
|
await fs14.promises.mkdir(this.persistDir, { recursive: true });
|
|
7500
|
-
const filePath =
|
|
7722
|
+
const filePath = path15.join(this.persistDir, "history.json");
|
|
7501
7723
|
const data = await fs14.promises.readFile(filePath, "utf-8");
|
|
7502
7724
|
const parsed = import_zod11.z.array(RunResultSchema).safeParse(JSON.parse(data));
|
|
7503
7725
|
if (parsed.success) {
|
|
@@ -7533,7 +7755,7 @@ var MaintenanceReporter = class {
|
|
|
7533
7755
|
async persist() {
|
|
7534
7756
|
try {
|
|
7535
7757
|
await fs14.promises.mkdir(this.persistDir, { recursive: true });
|
|
7536
|
-
const filePath =
|
|
7758
|
+
const filePath = path15.join(this.persistDir, "history.json");
|
|
7537
7759
|
await fs14.promises.writeFile(filePath, JSON.stringify(this.history, null, 2), "utf-8");
|
|
7538
7760
|
} catch (err) {
|
|
7539
7761
|
this.logger.error("MaintenanceReporter: failed to persist history", { error: String(err) });
|
|
@@ -7754,22 +7976,39 @@ var TaskRunner = class {
|
|
|
7754
7976
|
}
|
|
7755
7977
|
/**
|
|
7756
7978
|
* Housekeeping: run command directly, no AI, no PR.
|
|
7979
|
+
*
|
|
7980
|
+
* Captures stdout and parses a trailing JSON status line if present.
|
|
7981
|
+
* Recognized contracts:
|
|
7982
|
+
* - Phase 4/5 status contract (e.g., harness pulse run): success/skipped/failure/no-issues
|
|
7983
|
+
* - sync-main contract: updated/no-op/skipped/error → mapped onto the run-result status
|
|
7984
|
+
* Legacy housekeeping commands that emit no JSON keep the prior behavior:
|
|
7985
|
+
* status: 'success', findings: 0.
|
|
7757
7986
|
*/
|
|
7758
7987
|
async runHousekeeping(task, startedAt) {
|
|
7759
7988
|
if (!task.checkCommand || task.checkCommand.length === 0) {
|
|
7760
7989
|
return this.failureResult(task.id, startedAt, "housekeeping task missing checkCommand");
|
|
7761
7990
|
}
|
|
7762
|
-
|
|
7763
|
-
|
|
7991
|
+
let stdout;
|
|
7992
|
+
try {
|
|
7993
|
+
const out = await this.commandExecutor.exec(task.checkCommand, this.cwd);
|
|
7994
|
+
stdout = out.stdout ?? "";
|
|
7995
|
+
} catch (err) {
|
|
7996
|
+
return this.failureResult(task.id, startedAt, String(err));
|
|
7997
|
+
}
|
|
7998
|
+
const parsed = parseStatusLine(stdout);
|
|
7999
|
+
const status = parsed?.status ?? "success";
|
|
8000
|
+
const result = {
|
|
7764
8001
|
taskId: task.id,
|
|
7765
8002
|
startedAt,
|
|
7766
8003
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7767
|
-
status
|
|
8004
|
+
status,
|
|
7768
8005
|
findings: 0,
|
|
7769
8006
|
fixed: 0,
|
|
7770
8007
|
prUrl: null,
|
|
7771
8008
|
prUpdated: false
|
|
7772
8009
|
};
|
|
8010
|
+
if (parsed?.error) result.error = parsed.error;
|
|
8011
|
+
return result;
|
|
7773
8012
|
}
|
|
7774
8013
|
/**
|
|
7775
8014
|
* Resolve which AI backend name to use for a given task.
|
|
@@ -7803,7 +8042,7 @@ function parseStatusLine(output) {
|
|
|
7803
8042
|
const obj = JSON.parse(line);
|
|
7804
8043
|
const s = obj.status;
|
|
7805
8044
|
if (s === "success" || s === "skipped" || s === "failure" || s === "no-issues") {
|
|
7806
|
-
const parsed = { status: s };
|
|
8045
|
+
const parsed = { status: s, rawStatus: s };
|
|
7807
8046
|
if (typeof obj.candidatesFound === "number") {
|
|
7808
8047
|
parsed.candidatesFound = obj.candidatesFound;
|
|
7809
8048
|
}
|
|
@@ -7813,8 +8052,18 @@ function parseStatusLine(output) {
|
|
|
7813
8052
|
if (typeof obj.reason === "string") {
|
|
7814
8053
|
parsed.reason = obj.reason;
|
|
7815
8054
|
}
|
|
8055
|
+
if (typeof obj.detail === "string" && !parsed.error) {
|
|
8056
|
+
parsed.error = `${parsed.reason ?? "skipped"}: ${obj.detail}`;
|
|
8057
|
+
}
|
|
7816
8058
|
return parsed;
|
|
7817
8059
|
}
|
|
8060
|
+
if (s === "updated" || s === "no-op") {
|
|
8061
|
+
return { status: "success", rawStatus: s };
|
|
8062
|
+
}
|
|
8063
|
+
if (s === "error") {
|
|
8064
|
+
const message = typeof obj.message === "string" ? obj.message : "unknown error";
|
|
8065
|
+
return { status: "failure", error: message, rawStatus: "error" };
|
|
8066
|
+
}
|
|
7818
8067
|
} catch {
|
|
7819
8068
|
}
|
|
7820
8069
|
}
|
|
@@ -7895,11 +8144,15 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
7895
8144
|
completionHandler;
|
|
7896
8145
|
/** Project root directory, derived from workspace root. */
|
|
7897
8146
|
get projectRoot() {
|
|
7898
|
-
return
|
|
8147
|
+
return path16.resolve(this.config.workspace.root, "..", "..");
|
|
7899
8148
|
}
|
|
7900
8149
|
enrichedSpecsByIssue = /* @__PURE__ */ new Map();
|
|
7901
8150
|
/** Tracks recently-failed intelligence analysis to avoid re-requesting every tick */
|
|
7902
8151
|
analysisFailureCache = /* @__PURE__ */ new Map();
|
|
8152
|
+
// Phase 3 added a private `roadmapMode` field used by `createTracker` to
|
|
8153
|
+
// guard the file-less stub. Phase 4 / S2 / D-P4-E shifted dispatch onto
|
|
8154
|
+
// `tracker.kind`, removing the need for the field — it is now dropped to
|
|
8155
|
+
// satisfy `noUnusedLocals`. See decision D-P3-orchestrator-mode-via-fs-read.
|
|
7903
8156
|
/** Abort controllers and PIDs for running agent tasks — used by stopIssue to cancel in-flight work.
|
|
7904
8157
|
* The PID is stored here because the running entry may be deleted by the state machine
|
|
7905
8158
|
* before the stop effect executes (e.g., stall_detected removes the entry first). */
|
|
@@ -7935,14 +8188,19 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
7935
8188
|
);
|
|
7936
8189
|
}
|
|
7937
8190
|
this.tracker = overrides?.tracker || this.createTracker();
|
|
7938
|
-
this.workspace = new WorkspaceManager(config.workspace
|
|
8191
|
+
this.workspace = new WorkspaceManager(config.workspace, {
|
|
8192
|
+
emitEvent: (event) => {
|
|
8193
|
+
this.server?.broadcastMaintenance("maintenance:baseref_fallback", event);
|
|
8194
|
+
this.emit("maintenance:baseref_fallback", event);
|
|
8195
|
+
}
|
|
8196
|
+
});
|
|
7939
8197
|
this.hooks = new WorkspaceHooks(config.hooks);
|
|
7940
8198
|
this.renderer = new PromptRenderer();
|
|
7941
8199
|
this.overrideBackend = overrides?.backend ?? null;
|
|
7942
8200
|
this.interactionQueue = new InteractionQueue(
|
|
7943
|
-
|
|
8201
|
+
path16.join(config.workspace.root, "..", "interactions")
|
|
7944
8202
|
);
|
|
7945
|
-
this.analysisArchive = new AnalysisArchive(
|
|
8203
|
+
this.analysisArchive = new AnalysisArchive(path16.join(config.workspace.root, "..", "analyses"));
|
|
7946
8204
|
const backendsMap = this.config.agent.backends ?? {};
|
|
7947
8205
|
for (const [name, def] of Object.entries(backendsMap)) {
|
|
7948
8206
|
if (def.type === "local" || def.type === "pi") {
|
|
@@ -7984,7 +8242,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
7984
8242
|
...overrides?.execFileFn ? { execFileFn: overrides.execFileFn } : {}
|
|
7985
8243
|
});
|
|
7986
8244
|
this.recorder = new StreamRecorder(
|
|
7987
|
-
|
|
8245
|
+
path16.resolve(config.workspace.root, "..", "streams"),
|
|
7988
8246
|
this.logger
|
|
7989
8247
|
);
|
|
7990
8248
|
const self = this;
|
|
@@ -8016,7 +8274,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
8016
8274
|
if (config.server?.port) {
|
|
8017
8275
|
this.server = new OrchestratorServer(this, config.server.port, {
|
|
8018
8276
|
interactionQueue: this.interactionQueue,
|
|
8019
|
-
plansDir:
|
|
8277
|
+
plansDir: path16.resolve(config.workspace.root, "..", "docs", "plans"),
|
|
8020
8278
|
pipeline: this.pipeline,
|
|
8021
8279
|
analysisArchive: this.analysisArchive,
|
|
8022
8280
|
roadmapPath: config.tracker.filePath ?? null,
|
|
@@ -8047,6 +8305,17 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
8047
8305
|
}
|
|
8048
8306
|
}
|
|
8049
8307
|
createTracker() {
|
|
8308
|
+
if (this.config.tracker.kind === "github-issues") {
|
|
8309
|
+
const trackerCfg = {
|
|
8310
|
+
kind: "github-issues",
|
|
8311
|
+
repo: this.config.tracker.projectSlug ?? "",
|
|
8312
|
+
...this.config.tracker.apiKey ? { token: this.config.tracker.apiKey } : {},
|
|
8313
|
+
...this.config.tracker.endpoint ? { apiBase: this.config.tracker.endpoint } : {}
|
|
8314
|
+
};
|
|
8315
|
+
const clientResult = (0, import_core11.createTrackerClient)(trackerCfg);
|
|
8316
|
+
if (!clientResult.ok) throw clientResult.error;
|
|
8317
|
+
return new GitHubIssuesIssueTrackerAdapter(clientResult.value, this.config.tracker);
|
|
8318
|
+
}
|
|
8050
8319
|
if (this.config.tracker.kind === "roadmap") {
|
|
8051
8320
|
return new RoadmapTrackerAdapter(this.config.tracker);
|
|
8052
8321
|
}
|
|
@@ -8062,8 +8331,8 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
8062
8331
|
const checkRunner = {
|
|
8063
8332
|
run: async (command, cwd) => {
|
|
8064
8333
|
const { execFile: execFile6 } = await import("child_process");
|
|
8065
|
-
const { promisify:
|
|
8066
|
-
const execFileAsync =
|
|
8334
|
+
const { promisify: promisify4 } = await import("util");
|
|
8335
|
+
const execFileAsync = promisify4(execFile6);
|
|
8067
8336
|
const [cmd, ...args] = command;
|
|
8068
8337
|
if (!cmd) return { passed: true, findings: 0, output: "" };
|
|
8069
8338
|
try {
|
|
@@ -8097,12 +8366,13 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
8097
8366
|
const commandExecutor = {
|
|
8098
8367
|
exec: async (command, cwd) => {
|
|
8099
8368
|
const { execFile: execFile6 } = await import("child_process");
|
|
8100
|
-
const { promisify:
|
|
8101
|
-
const execFileAsync =
|
|
8369
|
+
const { promisify: promisify4 } = await import("util");
|
|
8370
|
+
const execFileAsync = promisify4(execFile6);
|
|
8102
8371
|
const [cmd, ...args] = command;
|
|
8103
|
-
if (!cmd) return;
|
|
8372
|
+
if (!cmd) return { stdout: "" };
|
|
8104
8373
|
try {
|
|
8105
|
-
await execFileAsync(cmd, args, { cwd, timeout: 12e4 });
|
|
8374
|
+
const { stdout } = await execFileAsync(cmd, args, { cwd, timeout: 12e4 });
|
|
8375
|
+
return { stdout: String(stdout) };
|
|
8106
8376
|
} catch (err) {
|
|
8107
8377
|
logger.warn("Maintenance command execution failed", {
|
|
8108
8378
|
command,
|
|
@@ -8127,7 +8397,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
8127
8397
|
*/
|
|
8128
8398
|
async initMaintenance(maintenanceConfig) {
|
|
8129
8399
|
this.maintenanceReporter = new MaintenanceReporter({
|
|
8130
|
-
persistDir:
|
|
8400
|
+
persistDir: path16.join(this.projectRoot, ".harness", "maintenance"),
|
|
8131
8401
|
logger: this.logger
|
|
8132
8402
|
});
|
|
8133
8403
|
await this.maintenanceReporter.load();
|
|
@@ -8135,7 +8405,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
8135
8405
|
const reporter = this.maintenanceReporter;
|
|
8136
8406
|
this.maintenanceScheduler = new MaintenanceScheduler({
|
|
8137
8407
|
config: maintenanceConfig,
|
|
8138
|
-
|
|
8408
|
+
leaderElector: new SingleProcessLeaderElector(),
|
|
8139
8409
|
logger: this.logger,
|
|
8140
8410
|
historyProvider: reporter,
|
|
8141
8411
|
onTaskDue: async (task) => {
|
|
@@ -8178,129 +8448,14 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
8178
8448
|
}
|
|
8179
8449
|
}
|
|
8180
8450
|
createIntelligencePipeline() {
|
|
8181
|
-
const
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
if (!selProvider) return null;
|
|
8185
|
-
const routing = this.config.agent.routing;
|
|
8186
|
-
const peslName = routing?.intelligence?.pesl;
|
|
8187
|
-
const selName = routing?.intelligence?.sel ?? routing?.default;
|
|
8188
|
-
const peslProvider = peslName !== void 0 && peslName !== selName ? this.createAnalysisProvider("pesl") : null;
|
|
8189
|
-
const peslModel = intel.models?.pesl ?? this.config.agent.model;
|
|
8190
|
-
const store = new import_graph.GraphStore();
|
|
8191
|
-
this.graphStore = store;
|
|
8192
|
-
return new import_intelligence4.IntelligencePipeline(selProvider, store, {
|
|
8193
|
-
...peslModel !== void 0 && { peslModel },
|
|
8194
|
-
...peslProvider !== null && peslProvider !== void 0 && { peslProvider }
|
|
8195
|
-
});
|
|
8196
|
-
}
|
|
8197
|
-
/**
|
|
8198
|
-
* Create the AnalysisProvider for an intelligence-pipeline layer
|
|
8199
|
-
* (`sel` by default; `pesl` when constructing a distinct PESL
|
|
8200
|
-
* provider per Spec 2 SC35).
|
|
8201
|
-
*
|
|
8202
|
-
* Spec 2 Phase 4 (SC31–SC36) — resolution order:
|
|
8203
|
-
* 1. Explicit `intelligence.provider` config wins (preserves Phase 0–3 behavior; SC33).
|
|
8204
|
-
* 2. Otherwise, consult `agent.routing.intelligence.<layer>` (or
|
|
8205
|
-
* `routing.default`) to pick a `BackendDef` from `agent.backends`,
|
|
8206
|
-
* then translate via `buildAnalysisProvider` (the per-type factory).
|
|
8207
|
-
*
|
|
8208
|
-
* Closes the Phase 2 deferral (P2-DEF-638): the legacy
|
|
8209
|
-
* `this.config.agent.backend` read at the bottom of this method is
|
|
8210
|
-
* removed; routing is the sole source for non-explicit configs.
|
|
8211
|
-
*
|
|
8212
|
-
* Cyclomatic complexity: pre-Phase-4 was 33 (factory dispatch was
|
|
8213
|
-
* inlined here). Phase 4 extracts the per-type tree into
|
|
8214
|
-
* `buildAnalysisProvider`, dropping this method to ≤ 5 branches
|
|
8215
|
-
* (under the 15 threshold).
|
|
8216
|
-
*/
|
|
8217
|
-
createAnalysisProvider(layer = "sel") {
|
|
8218
|
-
const intel = this.config.intelligence;
|
|
8219
|
-
if (!intel?.enabled) return null;
|
|
8220
|
-
if (intel.provider) {
|
|
8221
|
-
const layerModel = layer === "sel" ? intel.models?.sel : intel.models?.pesl;
|
|
8222
|
-
return this.createProviderFromExplicitConfig(
|
|
8223
|
-
intel.provider,
|
|
8224
|
-
layerModel ?? this.config.agent.model
|
|
8225
|
-
);
|
|
8226
|
-
}
|
|
8227
|
-
const routed = this.resolveRoutedBackendForIntelligence(layer);
|
|
8228
|
-
if (!routed) return null;
|
|
8229
|
-
const { name, def } = routed;
|
|
8230
|
-
const resolver = this.localResolvers.get(name);
|
|
8231
|
-
return buildAnalysisProvider({
|
|
8232
|
-
def,
|
|
8233
|
-
backendName: name,
|
|
8234
|
-
layer,
|
|
8235
|
-
// Spec 2 P3-IMP-1: a single snapshot read feeds the factory's
|
|
8236
|
-
// unavailable-warn diagnostic (Configured/Detected lists) and
|
|
8237
|
-
// collapses the two `getStatus()` calls flagged by P3-SUG-2.
|
|
8238
|
-
getResolverStatusSnapshot: () => {
|
|
8239
|
-
if (!resolver) return null;
|
|
8240
|
-
const status = resolver.getStatus();
|
|
8241
|
-
return {
|
|
8242
|
-
available: status.available,
|
|
8243
|
-
resolved: status.resolved,
|
|
8244
|
-
configured: status.configured,
|
|
8245
|
-
detected: status.detected
|
|
8246
|
-
};
|
|
8247
|
-
},
|
|
8248
|
-
intelligence: intel,
|
|
8451
|
+
const bundle = buildIntelligencePipeline({
|
|
8452
|
+
config: this.config,
|
|
8453
|
+
localResolvers: this.localResolvers,
|
|
8249
8454
|
logger: this.logger
|
|
8250
8455
|
});
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
* back through `routing.intelligence.<layer>` → `routing.default`
|
|
8255
|
-
* → null. Returns the resolved name alongside the def so callers can
|
|
8256
|
-
* key into the per-name resolver map.
|
|
8257
|
-
*/
|
|
8258
|
-
resolveRoutedBackendForIntelligence(layer) {
|
|
8259
|
-
const routing = this.config.agent.routing;
|
|
8260
|
-
const backends = this.config.agent.backends;
|
|
8261
|
-
if (!routing || !backends) return null;
|
|
8262
|
-
const layerName = routing.intelligence?.[layer];
|
|
8263
|
-
const name = layerName ?? routing.default;
|
|
8264
|
-
const def = backends[name];
|
|
8265
|
-
if (!def) {
|
|
8266
|
-
this.logger.warn(
|
|
8267
|
-
`Intelligence pipeline: routed backend '${name}' for layer '${layer}' is not in agent.backends.`
|
|
8268
|
-
);
|
|
8269
|
-
return null;
|
|
8270
|
-
}
|
|
8271
|
-
return { name, def };
|
|
8272
|
-
}
|
|
8273
|
-
createProviderFromExplicitConfig(provider, selModel) {
|
|
8274
|
-
if (provider.kind === "anthropic") {
|
|
8275
|
-
const apiKey2 = provider.apiKey ?? this.config.agent.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
8276
|
-
if (!apiKey2) {
|
|
8277
|
-
throw new Error("Intelligence pipeline: no Anthropic API key found.");
|
|
8278
|
-
}
|
|
8279
|
-
return new import_intelligence4.AnthropicAnalysisProvider({
|
|
8280
|
-
apiKey: apiKey2,
|
|
8281
|
-
...selModel !== void 0 && { defaultModel: selModel }
|
|
8282
|
-
});
|
|
8283
|
-
}
|
|
8284
|
-
if (provider.kind === "claude-cli") {
|
|
8285
|
-
return new import_intelligence4.ClaudeCliAnalysisProvider({
|
|
8286
|
-
command: this.config.agent.command,
|
|
8287
|
-
...selModel !== void 0 && { defaultModel: selModel },
|
|
8288
|
-
...this.config.intelligence?.requestTimeoutMs !== void 0 && {
|
|
8289
|
-
timeoutMs: this.config.intelligence.requestTimeoutMs
|
|
8290
|
-
}
|
|
8291
|
-
});
|
|
8292
|
-
}
|
|
8293
|
-
const apiKey = provider.apiKey ?? this.config.agent.apiKey ?? "ollama";
|
|
8294
|
-
const baseUrl = provider.baseUrl ?? "http://localhost:11434/v1";
|
|
8295
|
-
const intel = this.config.intelligence;
|
|
8296
|
-
return new import_intelligence4.OpenAICompatibleAnalysisProvider({
|
|
8297
|
-
apiKey,
|
|
8298
|
-
baseUrl,
|
|
8299
|
-
...selModel !== void 0 && { defaultModel: selModel },
|
|
8300
|
-
...intel?.requestTimeoutMs !== void 0 && { timeoutMs: intel.requestTimeoutMs },
|
|
8301
|
-
...intel?.promptSuffix !== void 0 && { promptSuffix: intel.promptSuffix },
|
|
8302
|
-
...intel?.jsonMode !== void 0 && { jsonMode: intel.jsonMode }
|
|
8303
|
-
});
|
|
8456
|
+
if (!bundle) return null;
|
|
8457
|
+
this.graphStore = bundle.graphStore;
|
|
8458
|
+
return bundle.pipeline;
|
|
8304
8459
|
}
|
|
8305
8460
|
/**
|
|
8306
8461
|
* Lazily initializes the ClaimManager if it hasn't been created yet.
|
|
@@ -8681,12 +8836,12 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
8681
8836
|
async postLifecycleComment(identifier, externalId, event) {
|
|
8682
8837
|
try {
|
|
8683
8838
|
if (!externalId) return;
|
|
8684
|
-
const trackerConfig = (0,
|
|
8839
|
+
const trackerConfig = (0, import_core11.loadTrackerSyncConfig)(this.projectRoot);
|
|
8685
8840
|
if (!trackerConfig) return;
|
|
8686
8841
|
const token = process.env.GITHUB_TOKEN;
|
|
8687
8842
|
if (!token) return;
|
|
8688
8843
|
const orchestratorId = await this.orchestratorIdPromise;
|
|
8689
|
-
const adapter = new
|
|
8844
|
+
const adapter = new import_core11.GitHubIssuesSyncAdapter({ token, config: trackerConfig });
|
|
8690
8845
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
8691
8846
|
const actionMap = {
|
|
8692
8847
|
claimed: "Dispatching agent for autonomous execution",
|
|
@@ -8759,7 +8914,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
8759
8914
|
...f.line !== void 0 ? { line: f.line } : {}
|
|
8760
8915
|
}))
|
|
8761
8916
|
);
|
|
8762
|
-
(0,
|
|
8917
|
+
(0, import_core10.writeTaint)(
|
|
8763
8918
|
workspacePath,
|
|
8764
8919
|
issue.id,
|
|
8765
8920
|
"Medium-severity injection patterns found in workspace config files",
|
|
@@ -9360,6 +9515,150 @@ function launchTUI(orchestrator) {
|
|
|
9360
9515
|
const { waitUntilExit } = (0, import_ink5.render)(/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Dashboard, { orchestrator }));
|
|
9361
9516
|
return { waitUntilExit };
|
|
9362
9517
|
}
|
|
9518
|
+
|
|
9519
|
+
// src/maintenance/sync-main.ts
|
|
9520
|
+
var import_node_child_process9 = require("child_process");
|
|
9521
|
+
var import_node_util3 = require("util");
|
|
9522
|
+
var DEFAULT_TIMEOUT_MS2 = 6e4;
|
|
9523
|
+
async function git(execFileFn, args, cwd, timeoutMs) {
|
|
9524
|
+
const exec = (0, import_node_util3.promisify)(execFileFn);
|
|
9525
|
+
const { stdout, stderr } = await exec("git", args, { cwd, timeout: timeoutMs });
|
|
9526
|
+
return { stdout: String(stdout), stderr: String(stderr) };
|
|
9527
|
+
}
|
|
9528
|
+
function isSpawnEnoent(err) {
|
|
9529
|
+
if (!err || typeof err !== "object") return false;
|
|
9530
|
+
const code = err.code;
|
|
9531
|
+
return code === "ENOENT";
|
|
9532
|
+
}
|
|
9533
|
+
async function refExists(execFileFn, ref, cwd, timeoutMs) {
|
|
9534
|
+
try {
|
|
9535
|
+
await git(execFileFn, ["rev-parse", "--verify", "--quiet", ref], cwd, timeoutMs);
|
|
9536
|
+
return true;
|
|
9537
|
+
} catch (err) {
|
|
9538
|
+
if (isSpawnEnoent(err)) throw err;
|
|
9539
|
+
return false;
|
|
9540
|
+
}
|
|
9541
|
+
}
|
|
9542
|
+
async function resolveOriginDefault(execFileFn, cwd, timeoutMs) {
|
|
9543
|
+
try {
|
|
9544
|
+
const { stdout } = await git(
|
|
9545
|
+
execFileFn,
|
|
9546
|
+
["symbolic-ref", "--short", "refs/remotes/origin/HEAD"],
|
|
9547
|
+
cwd,
|
|
9548
|
+
timeoutMs
|
|
9549
|
+
);
|
|
9550
|
+
const v = stdout.trim();
|
|
9551
|
+
if (v) return v;
|
|
9552
|
+
} catch (err) {
|
|
9553
|
+
if (isSpawnEnoent(err)) throw err;
|
|
9554
|
+
}
|
|
9555
|
+
for (const candidate of ["origin/main", "origin/master"]) {
|
|
9556
|
+
if (await refExists(execFileFn, candidate, cwd, timeoutMs)) return candidate;
|
|
9557
|
+
}
|
|
9558
|
+
return null;
|
|
9559
|
+
}
|
|
9560
|
+
function shortName(originRef) {
|
|
9561
|
+
return originRef.startsWith("origin/") ? originRef.slice("origin/".length) : originRef;
|
|
9562
|
+
}
|
|
9563
|
+
function isDirtyConflictStderr(s) {
|
|
9564
|
+
return /would be overwritten|local changes|Aborting/i.test(s);
|
|
9565
|
+
}
|
|
9566
|
+
function extractStderr(err) {
|
|
9567
|
+
if (err && typeof err === "object" && "stderr" in err) {
|
|
9568
|
+
const raw = err.stderr;
|
|
9569
|
+
if (typeof raw === "string") return raw;
|
|
9570
|
+
if (raw instanceof Buffer) return raw.toString("utf8");
|
|
9571
|
+
}
|
|
9572
|
+
if (err instanceof Error) return err.message;
|
|
9573
|
+
if (typeof err === "string") return err;
|
|
9574
|
+
return "";
|
|
9575
|
+
}
|
|
9576
|
+
async function isAncestor(execFileFn, a, b, cwd, timeoutMs) {
|
|
9577
|
+
try {
|
|
9578
|
+
await git(execFileFn, ["merge-base", "--is-ancestor", a, b], cwd, timeoutMs);
|
|
9579
|
+
return true;
|
|
9580
|
+
} catch (err) {
|
|
9581
|
+
if (isSpawnEnoent(err)) throw err;
|
|
9582
|
+
return false;
|
|
9583
|
+
}
|
|
9584
|
+
}
|
|
9585
|
+
async function syncMain(repoRoot, opts = {}) {
|
|
9586
|
+
const execFileFn = opts.execFileFn ?? import_node_child_process9.execFile;
|
|
9587
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
9588
|
+
try {
|
|
9589
|
+
const originRef = await resolveOriginDefault(execFileFn, repoRoot, timeoutMs);
|
|
9590
|
+
if (!originRef) {
|
|
9591
|
+
return {
|
|
9592
|
+
status: "skipped",
|
|
9593
|
+
reason: "no-remote",
|
|
9594
|
+
detail: "origin/HEAD unset and neither origin/main nor origin/master resolves",
|
|
9595
|
+
defaultBranch: ""
|
|
9596
|
+
};
|
|
9597
|
+
}
|
|
9598
|
+
const defaultBranch = shortName(originRef);
|
|
9599
|
+
const { stdout: currentRaw } = await git(
|
|
9600
|
+
execFileFn,
|
|
9601
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
9602
|
+
repoRoot,
|
|
9603
|
+
timeoutMs
|
|
9604
|
+
);
|
|
9605
|
+
const current = currentRaw.trim();
|
|
9606
|
+
if (current !== defaultBranch) {
|
|
9607
|
+
return {
|
|
9608
|
+
status: "skipped",
|
|
9609
|
+
reason: "wrong-branch",
|
|
9610
|
+
detail: `current branch '${current}' is not the default '${defaultBranch}'`,
|
|
9611
|
+
defaultBranch
|
|
9612
|
+
};
|
|
9613
|
+
}
|
|
9614
|
+
try {
|
|
9615
|
+
await git(execFileFn, ["fetch", "origin", defaultBranch, "--quiet"], repoRoot, timeoutMs);
|
|
9616
|
+
} catch (err) {
|
|
9617
|
+
if (isSpawnEnoent(err)) throw err;
|
|
9618
|
+
return {
|
|
9619
|
+
status: "skipped",
|
|
9620
|
+
reason: "fetch-failed",
|
|
9621
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
9622
|
+
defaultBranch
|
|
9623
|
+
};
|
|
9624
|
+
}
|
|
9625
|
+
const headIsAncestor = await isAncestor(execFileFn, "HEAD", originRef, repoRoot, timeoutMs);
|
|
9626
|
+
const originIsAncestor = await isAncestor(execFileFn, originRef, "HEAD", repoRoot, timeoutMs);
|
|
9627
|
+
if (headIsAncestor && originIsAncestor) {
|
|
9628
|
+
return { status: "no-op", defaultBranch };
|
|
9629
|
+
}
|
|
9630
|
+
if (!headIsAncestor) {
|
|
9631
|
+
return {
|
|
9632
|
+
status: "skipped",
|
|
9633
|
+
reason: "diverged",
|
|
9634
|
+
detail: `local '${defaultBranch}' has commits not on '${originRef}'`,
|
|
9635
|
+
defaultBranch
|
|
9636
|
+
};
|
|
9637
|
+
}
|
|
9638
|
+
const before = (await git(execFileFn, ["rev-parse", "HEAD"], repoRoot, timeoutMs)).stdout.trim();
|
|
9639
|
+
try {
|
|
9640
|
+
await git(execFileFn, ["merge", "--ff-only", originRef], repoRoot, timeoutMs);
|
|
9641
|
+
} catch (err) {
|
|
9642
|
+
const stderr = extractStderr(err);
|
|
9643
|
+
if (isDirtyConflictStderr(stderr)) {
|
|
9644
|
+
return {
|
|
9645
|
+
status: "skipped",
|
|
9646
|
+
reason: "dirty-conflict",
|
|
9647
|
+
detail: stderr.split("\n")[0] ?? "merge --ff-only failed due to working-tree changes",
|
|
9648
|
+
defaultBranch
|
|
9649
|
+
};
|
|
9650
|
+
}
|
|
9651
|
+
throw err;
|
|
9652
|
+
}
|
|
9653
|
+
const after = (await git(execFileFn, ["rev-parse", "HEAD"], repoRoot, timeoutMs)).stdout.trim();
|
|
9654
|
+
return { status: "updated", from: before, to: after, defaultBranch };
|
|
9655
|
+
} catch (err) {
|
|
9656
|
+
return {
|
|
9657
|
+
status: "error",
|
|
9658
|
+
message: err instanceof Error ? err.message : String(err)
|
|
9659
|
+
};
|
|
9660
|
+
}
|
|
9661
|
+
}
|
|
9363
9662
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9364
9663
|
0 && (module.exports = {
|
|
9365
9664
|
AnalysisArchive,
|
|
@@ -9404,6 +9703,7 @@ function launchTUI(orchestrator) {
|
|
|
9404
9703
|
savePublishedIndex,
|
|
9405
9704
|
selectCandidates,
|
|
9406
9705
|
sortCandidates,
|
|
9706
|
+
syncMain,
|
|
9407
9707
|
triageIssue,
|
|
9408
9708
|
validateWorkflowConfig
|
|
9409
9709
|
});
|