@harness-engineering/orchestrator 0.8.4 → 0.9.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 +674 -5
- package/dist/index.d.ts +674 -5
- package/dist/index.js +1159 -477
- package/dist/index.mjs +1196 -529
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -34,13 +34,18 @@ __export(index_exports, {
|
|
|
34
34
|
BUILT_IN_TASKS: () => BUILT_IN_TASKS,
|
|
35
35
|
BackendDefSchema: () => BackendDefSchema,
|
|
36
36
|
BackendRouter: () => BackendRouter,
|
|
37
|
+
CheckScriptRunner: () => CheckScriptRunner,
|
|
37
38
|
ClaimManager: () => ClaimManager,
|
|
38
39
|
GateNotReadyError: () => GateNotReadyError,
|
|
39
40
|
GateRunError: () => GateRunError,
|
|
40
41
|
InteractionQueue: () => InteractionQueue,
|
|
42
|
+
LinearGraphQLClient: () => LinearGraphQLClient,
|
|
41
43
|
LinearGraphQLStub: () => LinearGraphQLStub,
|
|
42
44
|
LocalModelResolver: () => LocalModelResolver,
|
|
45
|
+
MAINTENANCE_CHECK_MAX_BUFFER: () => MAINTENANCE_CHECK_MAX_BUFFER,
|
|
46
|
+
MAINTENANCE_CHECK_TIMEOUT_MS: () => MAINTENANCE_CHECK_TIMEOUT_MS,
|
|
43
47
|
MAX_ATTEMPTS: () => MAX_ATTEMPTS,
|
|
48
|
+
MaintenanceReporter: () => MaintenanceReporter,
|
|
44
49
|
MockBackend: () => MockBackend,
|
|
45
50
|
ORCHESTRATOR_IDENTITY_FILE: () => ORCHESTRATOR_IDENTITY_FILE,
|
|
46
51
|
Orchestrator: () => Orchestrator,
|
|
@@ -58,6 +63,7 @@ __export(index_exports, {
|
|
|
58
63
|
SqliteSearchIndex: () => SqliteSearchIndex,
|
|
59
64
|
StreamRecorder: () => StreamRecorder,
|
|
60
65
|
TaskOutputStore: () => TaskOutputStore,
|
|
66
|
+
TaskRunner: () => TaskRunner,
|
|
61
67
|
TokenStore: () => TokenStore,
|
|
62
68
|
WebhookQueue: () => WebhookQueue,
|
|
63
69
|
WorkflowLoader: () => WorkflowLoader,
|
|
@@ -68,7 +74,9 @@ __export(index_exports, {
|
|
|
68
74
|
buildArchiveHooks: () => buildArchiveHooks,
|
|
69
75
|
calculateRetryDelay: () => calculateRetryDelay,
|
|
70
76
|
canDispatch: () => canDispatch,
|
|
77
|
+
classifyCheckExecutionFailure: () => classifyCheckExecutionFailure,
|
|
71
78
|
computeRateLimitDelay: () => computeRateLimitDelay,
|
|
79
|
+
createAgentDispatcher: () => createAgentDispatcher,
|
|
72
80
|
createBackend: () => createBackend,
|
|
73
81
|
createEmptyState: () => createEmptyState,
|
|
74
82
|
crossFieldRoutingIssues: () => crossFieldRoutingIssues,
|
|
@@ -80,22 +88,27 @@ __export(index_exports, {
|
|
|
80
88
|
emitProposalApproved: () => emitProposalApproved,
|
|
81
89
|
emitProposalCreated: () => emitProposalCreated,
|
|
82
90
|
emitProposalRejected: () => emitProposalRejected,
|
|
91
|
+
explicitFindingsCount: () => explicitFindingsCount,
|
|
83
92
|
extractHighlights: () => extractHighlights,
|
|
84
93
|
extractTitlePrefix: () => extractTitlePrefix,
|
|
85
94
|
getAvailableSlots: () => getAvailableSlots,
|
|
86
95
|
getDefaultConfig: () => getDefaultConfig,
|
|
87
96
|
getPerStateCount: () => getPerStateCount,
|
|
88
97
|
indexSessionDirectory: () => indexSessionDirectory,
|
|
98
|
+
isCheckTimeoutError: () => isCheckTimeoutError,
|
|
89
99
|
isEligible: () => isEligible,
|
|
90
100
|
isSummaryEnabled: () => isSummaryEnabled,
|
|
91
101
|
launchTUI: () => launchTUI,
|
|
92
102
|
loadPublishedIndex: () => loadPublishedIndex,
|
|
103
|
+
makeBackendResolver: () => makeBackendResolver,
|
|
93
104
|
migrateAgentConfig: () => migrateAgentConfig,
|
|
94
105
|
normalizeFts5Query: () => normalizeFts5Query,
|
|
106
|
+
normalizeHarnessCommand: () => normalizeHarnessCommand,
|
|
95
107
|
normalizeLocalModel: () => normalizeLocalModel,
|
|
96
108
|
openSearchIndex: () => openSearchIndex,
|
|
97
109
|
promote: () => promote,
|
|
98
110
|
reconcile: () => reconcile,
|
|
111
|
+
recoverFindingsCount: () => recoverFindingsCount,
|
|
99
112
|
reindexFromArchive: () => reindexFromArchive,
|
|
100
113
|
renderAnalysisComment: () => renderAnalysisComment,
|
|
101
114
|
renderLlmSummaryMarkdown: () => renderLlmSummaryMarkdown,
|
|
@@ -105,9 +118,11 @@ __export(index_exports, {
|
|
|
105
118
|
routeIssue: () => routeIssue,
|
|
106
119
|
routingWarnings: () => routingWarnings,
|
|
107
120
|
runGate: () => runGate,
|
|
121
|
+
runHarnessCheck: () => runHarnessCheck,
|
|
108
122
|
savePublishedIndex: () => savePublishedIndex,
|
|
109
123
|
searchIndexPath: () => searchIndexPath,
|
|
110
124
|
selectCandidates: () => selectCandidates,
|
|
125
|
+
selectTasks: () => selectTasks,
|
|
111
126
|
sortCandidates: () => sortCandidates,
|
|
112
127
|
summarizeArchivedSession: () => summarizeArchivedSession,
|
|
113
128
|
syncMain: () => syncMain,
|
|
@@ -2327,7 +2342,6 @@ var WorkflowLoader = class {
|
|
|
2327
2342
|
};
|
|
2328
2343
|
|
|
2329
2344
|
// src/tracker/adapters/roadmap.ts
|
|
2330
|
-
var fs8 = __toESM(require("fs/promises"));
|
|
2331
2345
|
var import_node_crypto2 = require("crypto");
|
|
2332
2346
|
var import_core = require("@harness-engineering/core");
|
|
2333
2347
|
var import_types4 = require("@harness-engineering/types");
|
|
@@ -2358,8 +2372,7 @@ var RoadmapTrackerAdapter = class {
|
|
|
2358
2372
|
async fetchIssuesByStates(stateNames) {
|
|
2359
2373
|
try {
|
|
2360
2374
|
if (!this.config.filePath) return (0, import_types4.Err)(new Error("Missing filePath"));
|
|
2361
|
-
const
|
|
2362
|
-
const roadmapResult = (0, import_core.parseRoadmap)(content);
|
|
2375
|
+
const roadmapResult = await this.loadRoadmap();
|
|
2363
2376
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2364
2377
|
const issues = [];
|
|
2365
2378
|
for (const milestone of roadmapResult.value.milestones) {
|
|
@@ -2390,17 +2403,19 @@ var RoadmapTrackerAdapter = class {
|
|
|
2390
2403
|
if (!terminal) {
|
|
2391
2404
|
return (0, import_types4.Err)(new Error("Tracker config has no terminalStates; cannot mark complete"));
|
|
2392
2405
|
}
|
|
2393
|
-
const
|
|
2394
|
-
const roadmapResult =
|
|
2406
|
+
const store = this.resolveStore();
|
|
2407
|
+
const roadmapResult = await store.load();
|
|
2395
2408
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2396
2409
|
const roadmap = roadmapResult.value;
|
|
2410
|
+
const before = structuredClone(roadmap);
|
|
2397
2411
|
const target = this.findFeatureById(roadmap.milestones, issueId);
|
|
2398
2412
|
if (!target) return (0, import_types4.Ok)(void 0);
|
|
2399
2413
|
const normalizedTerminal = this.config.terminalStates.map((s) => s.toLowerCase());
|
|
2400
2414
|
if (normalizedTerminal.includes(target.status.toLowerCase())) return (0, import_types4.Ok)(void 0);
|
|
2401
2415
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2402
2416
|
(0, import_core.setStatus)(roadmap, target, terminal, now.slice(0, 10));
|
|
2403
|
-
await
|
|
2417
|
+
const persisted = await (0, import_core.applyRoadmapDiff)(store, before, roadmap);
|
|
2418
|
+
if (!persisted.ok) return persisted;
|
|
2404
2419
|
return (0, import_types4.Ok)(void 0);
|
|
2405
2420
|
} catch (error) {
|
|
2406
2421
|
return (0, import_types4.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -2414,10 +2429,11 @@ var RoadmapTrackerAdapter = class {
|
|
|
2414
2429
|
async claimIssue(issueId, orchestratorId) {
|
|
2415
2430
|
try {
|
|
2416
2431
|
if (!this.config.filePath) return (0, import_types4.Err)(new Error("Missing filePath"));
|
|
2417
|
-
const
|
|
2418
|
-
const roadmapResult =
|
|
2432
|
+
const store = this.resolveStore();
|
|
2433
|
+
const roadmapResult = await store.load();
|
|
2419
2434
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2420
2435
|
const roadmap = roadmapResult.value;
|
|
2436
|
+
const before = structuredClone(roadmap);
|
|
2421
2437
|
const target = this.findFeatureById(roadmap.milestones, issueId);
|
|
2422
2438
|
if (!target) return (0, import_types4.Ok)(void 0);
|
|
2423
2439
|
if (!(0, import_core.isClaimableBy)(target, orchestratorId)) {
|
|
@@ -2429,7 +2445,8 @@ var RoadmapTrackerAdapter = class {
|
|
|
2429
2445
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2430
2446
|
(0, import_core.claim)(roadmap, target, orchestratorId, now.slice(0, 10));
|
|
2431
2447
|
target.updatedAt = now;
|
|
2432
|
-
await
|
|
2448
|
+
const persisted = await (0, import_core.applyRoadmapDiff)(store, before, roadmap);
|
|
2449
|
+
if (!persisted.ok) return persisted;
|
|
2433
2450
|
return (0, import_types4.Ok)(void 0);
|
|
2434
2451
|
} catch (error) {
|
|
2435
2452
|
return (0, import_types4.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -2446,10 +2463,11 @@ var RoadmapTrackerAdapter = class {
|
|
|
2446
2463
|
if (!activeState) {
|
|
2447
2464
|
return (0, import_types4.Err)(new Error("Tracker config has no activeStates; cannot release"));
|
|
2448
2465
|
}
|
|
2449
|
-
const
|
|
2450
|
-
const roadmapResult =
|
|
2466
|
+
const store = this.resolveStore();
|
|
2467
|
+
const roadmapResult = await store.load();
|
|
2451
2468
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2452
2469
|
const roadmap = roadmapResult.value;
|
|
2470
|
+
const before = structuredClone(roadmap);
|
|
2453
2471
|
const target = this.findFeatureById(roadmap.milestones, issueId);
|
|
2454
2472
|
if (!target) return (0, import_types4.Ok)(void 0);
|
|
2455
2473
|
if (this.config.activeStates.includes(target.status) && target.assignee === null) {
|
|
@@ -2458,12 +2476,26 @@ var RoadmapTrackerAdapter = class {
|
|
|
2458
2476
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2459
2477
|
(0, import_core.setStatus)(roadmap, target, activeState, now.slice(0, 10));
|
|
2460
2478
|
target.updatedAt = null;
|
|
2461
|
-
await
|
|
2479
|
+
const persisted = await (0, import_core.applyRoadmapDiff)(store, before, roadmap);
|
|
2480
|
+
if (!persisted.ok) return persisted;
|
|
2462
2481
|
return (0, import_types4.Ok)(void 0);
|
|
2463
2482
|
} catch (error) {
|
|
2464
2483
|
return (0, import_types4.Err)(error instanceof Error ? error : new Error(String(error)));
|
|
2465
2484
|
}
|
|
2466
2485
|
}
|
|
2486
|
+
/**
|
|
2487
|
+
* Resolve the roadmap store anchored on the configured aggregate file path.
|
|
2488
|
+
* The shard backend is chosen when a sibling `roadmap.d/` exists next to the
|
|
2489
|
+
* file; otherwise the monolith file is read/written whole. Callers guard
|
|
2490
|
+
* `this.config.filePath` before invoking.
|
|
2491
|
+
*/
|
|
2492
|
+
resolveStore() {
|
|
2493
|
+
return (0, import_core.resolveRoadmapStoreForFile)({ roadmapPath: this.config.filePath });
|
|
2494
|
+
}
|
|
2495
|
+
/** Load the roadmap via the store (sharded or monolith), returning a Result. */
|
|
2496
|
+
loadRoadmap() {
|
|
2497
|
+
return this.resolveStore().load();
|
|
2498
|
+
}
|
|
2467
2499
|
findFeatureById(milestones, issueId) {
|
|
2468
2500
|
for (const milestone of milestones) {
|
|
2469
2501
|
for (const feature of milestone.features) {
|
|
@@ -2480,8 +2512,7 @@ var RoadmapTrackerAdapter = class {
|
|
|
2480
2512
|
async fetchIssueStatesByIds(issueIds) {
|
|
2481
2513
|
try {
|
|
2482
2514
|
if (!this.config.filePath) return (0, import_types4.Err)(new Error("Missing filePath"));
|
|
2483
|
-
const
|
|
2484
|
-
const roadmapResult = (0, import_core.parseRoadmap)(content);
|
|
2515
|
+
const roadmapResult = await this.loadRoadmap();
|
|
2485
2516
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2486
2517
|
const issueMap = /* @__PURE__ */ new Map();
|
|
2487
2518
|
for (const milestone of roadmapResult.value.milestones) {
|
|
@@ -2537,6 +2568,69 @@ var RoadmapTrackerAdapter = class {
|
|
|
2537
2568
|
|
|
2538
2569
|
// src/tracker/extensions/linear.ts
|
|
2539
2570
|
var import_types5 = require("@harness-engineering/types");
|
|
2571
|
+
var LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
|
|
2572
|
+
function errorMessage(err) {
|
|
2573
|
+
return err instanceof Error ? err.message : String(err);
|
|
2574
|
+
}
|
|
2575
|
+
var LinearGraphQLClient = class {
|
|
2576
|
+
apiKey;
|
|
2577
|
+
endpoint;
|
|
2578
|
+
fetchFn;
|
|
2579
|
+
constructor(opts) {
|
|
2580
|
+
this.apiKey = opts.apiKey;
|
|
2581
|
+
this.endpoint = opts.endpoint ?? LINEAR_GRAPHQL_ENDPOINT;
|
|
2582
|
+
this.fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
2583
|
+
}
|
|
2584
|
+
async query(query, variables) {
|
|
2585
|
+
const sent = await this.sendRequest(query, variables);
|
|
2586
|
+
if (!sent.ok) return sent;
|
|
2587
|
+
const res = sent.value;
|
|
2588
|
+
if (!res.ok) {
|
|
2589
|
+
return (0, import_types5.Err)(await this.httpError(res));
|
|
2590
|
+
}
|
|
2591
|
+
const parsed = await this.parseEnvelope(res);
|
|
2592
|
+
if (!parsed.ok) return parsed;
|
|
2593
|
+
const envelope = parsed.value;
|
|
2594
|
+
const graphqlError = envelopeError(envelope);
|
|
2595
|
+
if (graphqlError) return (0, import_types5.Err)(graphqlError);
|
|
2596
|
+
return (0, import_types5.Ok)(envelope.data ?? {});
|
|
2597
|
+
}
|
|
2598
|
+
/** POST the operation to Linear, normalizing transport throws into an `Err`. */
|
|
2599
|
+
async sendRequest(query, variables) {
|
|
2600
|
+
try {
|
|
2601
|
+
const res = await this.fetchFn(this.endpoint, {
|
|
2602
|
+
method: "POST",
|
|
2603
|
+
headers: {
|
|
2604
|
+
Authorization: this.apiKey,
|
|
2605
|
+
"Content-Type": "application/json"
|
|
2606
|
+
},
|
|
2607
|
+
body: JSON.stringify({ query, variables: variables ?? {} })
|
|
2608
|
+
});
|
|
2609
|
+
return (0, import_types5.Ok)(res);
|
|
2610
|
+
} catch (err) {
|
|
2611
|
+
return (0, import_types5.Err)(new Error(`Linear GraphQL request failed: ${errorMessage(err)}`));
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
/** Build the error for a non-2xx HTTP response, including a truncated body. */
|
|
2615
|
+
async httpError(res) {
|
|
2616
|
+
const body = await res.text().catch(() => "");
|
|
2617
|
+
const detail = body ? `: ${body.slice(0, 500)}` : "";
|
|
2618
|
+
return new Error(`Linear GraphQL HTTP ${res.status}${detail}`);
|
|
2619
|
+
}
|
|
2620
|
+
/** Parse the JSON envelope, normalizing parse failures into an `Err`. */
|
|
2621
|
+
async parseEnvelope(res) {
|
|
2622
|
+
try {
|
|
2623
|
+
return (0, import_types5.Ok)(await res.json());
|
|
2624
|
+
} catch (err) {
|
|
2625
|
+
return (0, import_types5.Err)(new Error(`Linear GraphQL response was not valid JSON: ${errorMessage(err)}`));
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
};
|
|
2629
|
+
function envelopeError(envelope) {
|
|
2630
|
+
if (!envelope.errors || envelope.errors.length === 0) return void 0;
|
|
2631
|
+
const message = envelope.errors.map((e) => e.message ?? "unknown error").join("; ");
|
|
2632
|
+
return new Error(`Linear GraphQL error: ${message}`);
|
|
2633
|
+
}
|
|
2540
2634
|
var LinearGraphQLStub = class {
|
|
2541
2635
|
async query(query, _variables) {
|
|
2542
2636
|
console.log("Linear GraphQL query (stub):", query);
|
|
@@ -2545,7 +2639,7 @@ var LinearGraphQLStub = class {
|
|
|
2545
2639
|
};
|
|
2546
2640
|
|
|
2547
2641
|
// src/workspace/manager.ts
|
|
2548
|
-
var
|
|
2642
|
+
var fs8 = __toESM(require("fs/promises"));
|
|
2549
2643
|
var path8 = __toESM(require("path"));
|
|
2550
2644
|
var import_node_child_process2 = require("child_process");
|
|
2551
2645
|
var import_node_util2 = require("util");
|
|
@@ -2585,7 +2679,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2585
2679
|
async getRepoRoot() {
|
|
2586
2680
|
if (this.repoRoot) return this.repoRoot;
|
|
2587
2681
|
const root = path8.resolve(this.config.root);
|
|
2588
|
-
await
|
|
2682
|
+
await fs8.mkdir(root, { recursive: true });
|
|
2589
2683
|
const stdout = await this.git(["rev-parse", "--show-toplevel"], root);
|
|
2590
2684
|
this.repoRoot = stdout.trim();
|
|
2591
2685
|
return this.repoRoot;
|
|
@@ -2598,21 +2692,21 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2598
2692
|
try {
|
|
2599
2693
|
const workspacePath = path8.resolve(this.resolvePath(identifier));
|
|
2600
2694
|
try {
|
|
2601
|
-
await
|
|
2695
|
+
await fs8.access(path8.join(workspacePath, ".git"));
|
|
2602
2696
|
const repoRoot2 = await this.getRepoRoot();
|
|
2603
2697
|
try {
|
|
2604
2698
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
|
|
2605
2699
|
} catch {
|
|
2606
|
-
await
|
|
2700
|
+
await fs8.rm(workspacePath, { recursive: true, force: true });
|
|
2607
2701
|
}
|
|
2608
2702
|
} catch {
|
|
2609
2703
|
try {
|
|
2610
|
-
await
|
|
2704
|
+
await fs8.access(workspacePath);
|
|
2611
2705
|
const repoRoot2 = await this.getRepoRoot();
|
|
2612
2706
|
try {
|
|
2613
2707
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
|
|
2614
2708
|
} catch {
|
|
2615
|
-
await
|
|
2709
|
+
await fs8.rm(workspacePath, { recursive: true, force: true });
|
|
2616
2710
|
}
|
|
2617
2711
|
} catch {
|
|
2618
2712
|
}
|
|
@@ -2665,13 +2759,13 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2665
2759
|
}
|
|
2666
2760
|
const src = path8.join(repoRoot, rel);
|
|
2667
2761
|
try {
|
|
2668
|
-
await
|
|
2762
|
+
await fs8.access(src);
|
|
2669
2763
|
} catch {
|
|
2670
2764
|
continue;
|
|
2671
2765
|
}
|
|
2672
2766
|
const dest = path8.join(workspacePath, rel);
|
|
2673
2767
|
try {
|
|
2674
|
-
await
|
|
2768
|
+
await fs8.cp(src, dest, { recursive: true, force: true });
|
|
2675
2769
|
} catch {
|
|
2676
2770
|
}
|
|
2677
2771
|
}
|
|
@@ -2747,7 +2841,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2747
2841
|
async exists(identifier) {
|
|
2748
2842
|
try {
|
|
2749
2843
|
const workspacePath = this.resolvePath(identifier);
|
|
2750
|
-
await
|
|
2844
|
+
await fs8.access(workspacePath);
|
|
2751
2845
|
return true;
|
|
2752
2846
|
} catch {
|
|
2753
2847
|
return false;
|
|
@@ -2762,7 +2856,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2762
2856
|
try {
|
|
2763
2857
|
const workspacePath = path8.resolve(this.resolvePath(identifier));
|
|
2764
2858
|
try {
|
|
2765
|
-
await
|
|
2859
|
+
await fs8.access(path8.join(workspacePath, ".git"));
|
|
2766
2860
|
} catch {
|
|
2767
2861
|
return null;
|
|
2768
2862
|
}
|
|
@@ -2873,7 +2967,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2873
2967
|
const repoRoot = await this.getRepoRoot();
|
|
2874
2968
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot);
|
|
2875
2969
|
} catch {
|
|
2876
|
-
await
|
|
2970
|
+
await fs8.rm(workspacePath, { recursive: true, force: true });
|
|
2877
2971
|
}
|
|
2878
2972
|
return (0, import_types6.Ok)(void 0);
|
|
2879
2973
|
} catch (error) {
|
|
@@ -3014,8 +3108,8 @@ var PromptRenderer = class {
|
|
|
3014
3108
|
// src/orchestrator.ts
|
|
3015
3109
|
var import_node_events = require("events");
|
|
3016
3110
|
var path21 = __toESM(require("path"));
|
|
3017
|
-
var
|
|
3018
|
-
var
|
|
3111
|
+
var import_node_crypto15 = require("crypto");
|
|
3112
|
+
var import_core15 = require("@harness-engineering/core");
|
|
3019
3113
|
|
|
3020
3114
|
// src/core/stall-detector.ts
|
|
3021
3115
|
function detectStalledIssues(running, nowMs, stallTimeoutMs) {
|
|
@@ -3591,7 +3685,7 @@ var CompletionHandler = class {
|
|
|
3591
3685
|
};
|
|
3592
3686
|
|
|
3593
3687
|
// src/orchestrator.ts
|
|
3594
|
-
var
|
|
3688
|
+
var import_core16 = require("@harness-engineering/core");
|
|
3595
3689
|
|
|
3596
3690
|
// src/tracker/adapters/github-issues-issue-tracker.ts
|
|
3597
3691
|
var import_types9 = require("@harness-engineering/types");
|
|
@@ -4724,18 +4818,18 @@ var AnthropicBackend = class {
|
|
|
4724
4818
|
usage
|
|
4725
4819
|
};
|
|
4726
4820
|
} catch (err) {
|
|
4727
|
-
const
|
|
4821
|
+
const errorMessage2 = err instanceof Error ? err.message : "Anthropic request failed";
|
|
4728
4822
|
yield {
|
|
4729
4823
|
type: "error",
|
|
4730
4824
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4731
|
-
content:
|
|
4825
|
+
content: errorMessage2,
|
|
4732
4826
|
sessionId: session.sessionId
|
|
4733
4827
|
};
|
|
4734
4828
|
return {
|
|
4735
4829
|
success: false,
|
|
4736
4830
|
sessionId: session.sessionId,
|
|
4737
4831
|
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
4738
|
-
error:
|
|
4832
|
+
error: errorMessage2
|
|
4739
4833
|
};
|
|
4740
4834
|
}
|
|
4741
4835
|
}
|
|
@@ -4826,18 +4920,18 @@ var OpenAIBackend = class {
|
|
|
4826
4920
|
}
|
|
4827
4921
|
}
|
|
4828
4922
|
} catch (err) {
|
|
4829
|
-
const
|
|
4923
|
+
const errorMessage2 = err instanceof Error ? err.message : "OpenAI request failed";
|
|
4830
4924
|
yield {
|
|
4831
4925
|
type: "error",
|
|
4832
4926
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4833
|
-
content:
|
|
4927
|
+
content: errorMessage2,
|
|
4834
4928
|
sessionId: session.sessionId
|
|
4835
4929
|
};
|
|
4836
4930
|
return {
|
|
4837
4931
|
success: false,
|
|
4838
4932
|
sessionId: session.sessionId,
|
|
4839
4933
|
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
4840
|
-
error:
|
|
4934
|
+
error: errorMessage2
|
|
4841
4935
|
};
|
|
4842
4936
|
}
|
|
4843
4937
|
const usage = {
|
|
@@ -4948,11 +5042,11 @@ var GeminiBackend = class {
|
|
|
4948
5042
|
}
|
|
4949
5043
|
}
|
|
4950
5044
|
} catch (err) {
|
|
4951
|
-
const
|
|
5045
|
+
const errorMessage2 = err instanceof Error ? err.message : "Gemini request failed";
|
|
4952
5046
|
yield {
|
|
4953
5047
|
type: "error",
|
|
4954
5048
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4955
|
-
content:
|
|
5049
|
+
content: errorMessage2,
|
|
4956
5050
|
sessionId: session.sessionId
|
|
4957
5051
|
};
|
|
4958
5052
|
return {
|
|
@@ -4965,7 +5059,7 @@ var GeminiBackend = class {
|
|
|
4965
5059
|
cacheCreationTokens,
|
|
4966
5060
|
cacheReadTokens
|
|
4967
5061
|
},
|
|
4968
|
-
error:
|
|
5062
|
+
error: errorMessage2
|
|
4969
5063
|
};
|
|
4970
5064
|
}
|
|
4971
5065
|
const usage = {
|
|
@@ -5091,18 +5185,18 @@ var LocalBackend = class {
|
|
|
5091
5185
|
}
|
|
5092
5186
|
}
|
|
5093
5187
|
} catch (err) {
|
|
5094
|
-
const
|
|
5188
|
+
const errorMessage2 = err instanceof Error ? err.message : "Local backend request failed";
|
|
5095
5189
|
yield {
|
|
5096
5190
|
type: "error",
|
|
5097
5191
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5098
|
-
content:
|
|
5192
|
+
content: errorMessage2,
|
|
5099
5193
|
sessionId: session.sessionId
|
|
5100
5194
|
};
|
|
5101
5195
|
return {
|
|
5102
5196
|
success: false,
|
|
5103
5197
|
sessionId: session.sessionId,
|
|
5104
5198
|
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
5105
|
-
error:
|
|
5199
|
+
error: errorMessage2
|
|
5106
5200
|
};
|
|
5107
5201
|
}
|
|
5108
5202
|
const usage = { inputTokens, outputTokens, totalTokens };
|
|
@@ -5438,30 +5532,8 @@ var SshBackend = class {
|
|
|
5438
5532
|
config;
|
|
5439
5533
|
spawnImpl;
|
|
5440
5534
|
constructor(config) {
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
}
|
|
5444
|
-
if (FORBIDDEN_HOST_CHARS.test(config.host) || config.host.startsWith("-")) {
|
|
5445
|
-
throw new Error(
|
|
5446
|
-
`SshBackend: invalid host '${config.host}' (contains shell metacharacters or starts with '-')`
|
|
5447
|
-
);
|
|
5448
|
-
}
|
|
5449
|
-
if (!config.remoteCommand || typeof config.remoteCommand !== "string") {
|
|
5450
|
-
throw new Error("SshBackend: `remoteCommand` is required");
|
|
5451
|
-
}
|
|
5452
|
-
if (config.user !== void 0 && /[\s;&|`$]/.test(config.user)) {
|
|
5453
|
-
throw new Error(`SshBackend: invalid user '${config.user}'`);
|
|
5454
|
-
}
|
|
5455
|
-
this.config = {
|
|
5456
|
-
host: config.host,
|
|
5457
|
-
remoteCommand: config.remoteCommand,
|
|
5458
|
-
sshBinary: config.sshBinary ?? "ssh",
|
|
5459
|
-
sshOptions: config.sshOptions ?? [],
|
|
5460
|
-
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
|
|
5461
|
-
...config.user !== void 0 ? { user: config.user } : {},
|
|
5462
|
-
...config.port !== void 0 ? { port: config.port } : {},
|
|
5463
|
-
...config.identityFile !== void 0 ? { identityFile: config.identityFile } : {}
|
|
5464
|
-
};
|
|
5535
|
+
validateSshConfig(config);
|
|
5536
|
+
this.config = normalizeSshConfig(config);
|
|
5465
5537
|
this.spawnImpl = config.spawnImpl ?? import_node_child_process5.spawn;
|
|
5466
5538
|
}
|
|
5467
5539
|
/**
|
|
@@ -5613,6 +5685,34 @@ var SshBackend = class {
|
|
|
5613
5685
|
});
|
|
5614
5686
|
}
|
|
5615
5687
|
};
|
|
5688
|
+
function validateSshConfig(config) {
|
|
5689
|
+
if (!config.host || typeof config.host !== "string") {
|
|
5690
|
+
throw new Error("SshBackend: `host` is required");
|
|
5691
|
+
}
|
|
5692
|
+
if (FORBIDDEN_HOST_CHARS.test(config.host) || config.host.startsWith("-")) {
|
|
5693
|
+
throw new Error(
|
|
5694
|
+
`SshBackend: invalid host '${config.host}' (contains shell metacharacters or starts with '-')`
|
|
5695
|
+
);
|
|
5696
|
+
}
|
|
5697
|
+
if (!config.remoteCommand || typeof config.remoteCommand !== "string") {
|
|
5698
|
+
throw new Error("SshBackend: `remoteCommand` is required");
|
|
5699
|
+
}
|
|
5700
|
+
if (config.user !== void 0 && /[\s;&|`$]/.test(config.user)) {
|
|
5701
|
+
throw new Error(`SshBackend: invalid user '${config.user}'`);
|
|
5702
|
+
}
|
|
5703
|
+
}
|
|
5704
|
+
function normalizeSshConfig(config) {
|
|
5705
|
+
return {
|
|
5706
|
+
host: config.host,
|
|
5707
|
+
remoteCommand: config.remoteCommand,
|
|
5708
|
+
sshBinary: config.sshBinary ?? "ssh",
|
|
5709
|
+
sshOptions: config.sshOptions ?? [],
|
|
5710
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
|
|
5711
|
+
...config.user !== void 0 ? { user: config.user } : {},
|
|
5712
|
+
...config.port !== void 0 ? { port: config.port } : {},
|
|
5713
|
+
...config.identityFile !== void 0 ? { identityFile: config.identityFile } : {}
|
|
5714
|
+
};
|
|
5715
|
+
}
|
|
5616
5716
|
function errResult(sessionId, message) {
|
|
5617
5717
|
return {
|
|
5618
5718
|
success: false,
|
|
@@ -5719,23 +5819,8 @@ var OciServerlessBackend = class extends ServerlessBackend {
|
|
|
5719
5819
|
envSource;
|
|
5720
5820
|
constructor(config) {
|
|
5721
5821
|
super();
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
}
|
|
5725
|
-
if (FORBIDDEN_IMAGE_CHARS.test(config.image) || config.image.startsWith("-")) {
|
|
5726
|
-
throw new Error(
|
|
5727
|
-
`OciServerlessBackend: invalid image '${config.image}' (contains shell metacharacters or starts with '-')`
|
|
5728
|
-
);
|
|
5729
|
-
}
|
|
5730
|
-
this.config = {
|
|
5731
|
-
image: config.image,
|
|
5732
|
-
pullPolicy: config.pullPolicy ?? "if-not-present",
|
|
5733
|
-
runtime: config.runtime ?? "docker",
|
|
5734
|
-
envPassthrough: config.envPassthrough ?? [],
|
|
5735
|
-
timeoutMs: config.timeoutMs ?? DEFAULT_OCI_TIMEOUT_MS,
|
|
5736
|
-
extraArgs: sanitizeExtraArgs(config.extraArgs),
|
|
5737
|
-
...config.registry !== void 0 ? { registry: config.registry } : {}
|
|
5738
|
-
};
|
|
5822
|
+
validateOciImage(config.image);
|
|
5823
|
+
this.config = resolveOciConfig(config);
|
|
5739
5824
|
this.spawnImpl = config.spawnImpl ?? import_node_child_process6.spawn;
|
|
5740
5825
|
this.envSource = config.envSource ?? process.env;
|
|
5741
5826
|
}
|
|
@@ -5898,6 +5983,27 @@ var OciServerlessBackend = class extends ServerlessBackend {
|
|
|
5898
5983
|
});
|
|
5899
5984
|
}
|
|
5900
5985
|
};
|
|
5986
|
+
function validateOciImage(image) {
|
|
5987
|
+
if (!image || typeof image !== "string") {
|
|
5988
|
+
throw new Error("OciServerlessBackend: `image` is required");
|
|
5989
|
+
}
|
|
5990
|
+
if (FORBIDDEN_IMAGE_CHARS.test(image) || image.startsWith("-")) {
|
|
5991
|
+
throw new Error(
|
|
5992
|
+
`OciServerlessBackend: invalid image '${image}' (contains shell metacharacters or starts with '-')`
|
|
5993
|
+
);
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5996
|
+
function resolveOciConfig(config) {
|
|
5997
|
+
return {
|
|
5998
|
+
image: config.image,
|
|
5999
|
+
pullPolicy: config.pullPolicy ?? "if-not-present",
|
|
6000
|
+
runtime: config.runtime ?? "docker",
|
|
6001
|
+
envPassthrough: config.envPassthrough ?? [],
|
|
6002
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_OCI_TIMEOUT_MS,
|
|
6003
|
+
extraArgs: sanitizeExtraArgs(config.extraArgs),
|
|
6004
|
+
...config.registry !== void 0 ? { registry: config.registry } : {}
|
|
6005
|
+
};
|
|
6006
|
+
}
|
|
5901
6007
|
function sanitizeExtraArgs(extraArgs) {
|
|
5902
6008
|
if (!extraArgs) return [];
|
|
5903
6009
|
return extraArgs.filter((arg) => !BLOCKED_DOCKER_FLAGS.some((flag) => arg.startsWith(flag)));
|
|
@@ -5969,78 +6075,96 @@ function makeGetModel(model) {
|
|
|
5969
6075
|
if (Array.isArray(model) && model.length > 0) return () => model[0] ?? null;
|
|
5970
6076
|
return () => null;
|
|
5971
6077
|
}
|
|
6078
|
+
function createClaudeBackend(def, options) {
|
|
6079
|
+
return new ClaudeBackend(def.command ?? "claude", {
|
|
6080
|
+
...options.cacheMetrics ? { cacheMetrics: options.cacheMetrics } : {}
|
|
6081
|
+
});
|
|
6082
|
+
}
|
|
6083
|
+
function createAnthropicBackend(def) {
|
|
6084
|
+
return new AnthropicBackend({
|
|
6085
|
+
model: def.model,
|
|
6086
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
6087
|
+
});
|
|
6088
|
+
}
|
|
6089
|
+
function createOpenAIBackend(def) {
|
|
6090
|
+
return new OpenAIBackend({
|
|
6091
|
+
model: def.model,
|
|
6092
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
6093
|
+
});
|
|
6094
|
+
}
|
|
6095
|
+
function createGeminiBackend(def) {
|
|
6096
|
+
return new GeminiBackend({
|
|
6097
|
+
model: def.model,
|
|
6098
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
6099
|
+
});
|
|
6100
|
+
}
|
|
6101
|
+
function createLocalBackend(def) {
|
|
6102
|
+
const isArray = Array.isArray(def.model);
|
|
6103
|
+
return new LocalBackend({
|
|
6104
|
+
endpoint: def.endpoint,
|
|
6105
|
+
...typeof def.model === "string" ? { model: def.model } : {},
|
|
6106
|
+
...isArray ? { getModel: makeGetModel(def.model) } : {},
|
|
6107
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {},
|
|
6108
|
+
...def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}
|
|
6109
|
+
});
|
|
6110
|
+
}
|
|
6111
|
+
function createPiBackend(def) {
|
|
6112
|
+
const isArray = Array.isArray(def.model);
|
|
6113
|
+
return new PiBackend({
|
|
6114
|
+
endpoint: def.endpoint,
|
|
6115
|
+
...typeof def.model === "string" ? { model: def.model } : {},
|
|
6116
|
+
...isArray ? { getModel: makeGetModel(def.model) } : {},
|
|
6117
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {},
|
|
6118
|
+
...def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}
|
|
6119
|
+
});
|
|
6120
|
+
}
|
|
6121
|
+
function createSshBackend(def) {
|
|
6122
|
+
return new SshBackend({
|
|
6123
|
+
host: def.host,
|
|
6124
|
+
remoteCommand: def.remoteCommand,
|
|
6125
|
+
...def.user !== void 0 ? { user: def.user } : {},
|
|
6126
|
+
...def.port !== void 0 ? { port: def.port } : {},
|
|
6127
|
+
...def.identityFile !== void 0 ? { identityFile: def.identityFile } : {},
|
|
6128
|
+
...def.sshOptions !== void 0 ? { sshOptions: def.sshOptions } : {},
|
|
6129
|
+
...def.sshBinary !== void 0 ? { sshBinary: def.sshBinary } : {}
|
|
6130
|
+
});
|
|
6131
|
+
}
|
|
6132
|
+
function createServerlessBackend(def) {
|
|
6133
|
+
switch (def.adapter) {
|
|
6134
|
+
case "oci":
|
|
6135
|
+
return new OciServerlessBackend({
|
|
6136
|
+
image: def.image,
|
|
6137
|
+
...def.registry !== void 0 ? { registry: def.registry } : {},
|
|
6138
|
+
...def.pullPolicy !== void 0 ? { pullPolicy: def.pullPolicy } : {},
|
|
6139
|
+
...def.envPassthrough !== void 0 ? { envPassthrough: def.envPassthrough } : {},
|
|
6140
|
+
...def.runtime !== void 0 ? { runtime: def.runtime } : {}
|
|
6141
|
+
});
|
|
6142
|
+
default: {
|
|
6143
|
+
const exhaustive = def.adapter;
|
|
6144
|
+
throw new Error(`createBackend: unknown serverless adapter ${JSON.stringify(exhaustive)}`);
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
5972
6148
|
function createBackend(def, options = {}) {
|
|
5973
6149
|
switch (def.type) {
|
|
5974
6150
|
case "mock":
|
|
5975
6151
|
return new MockBackend();
|
|
5976
6152
|
case "claude":
|
|
5977
|
-
return
|
|
5978
|
-
...options.cacheMetrics ? { cacheMetrics: options.cacheMetrics } : {}
|
|
5979
|
-
});
|
|
6153
|
+
return createClaudeBackend(def, options);
|
|
5980
6154
|
case "anthropic":
|
|
5981
|
-
return
|
|
5982
|
-
model: def.model,
|
|
5983
|
-
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
5984
|
-
});
|
|
6155
|
+
return createAnthropicBackend(def);
|
|
5985
6156
|
case "openai":
|
|
5986
|
-
return
|
|
5987
|
-
model: def.model,
|
|
5988
|
-
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
5989
|
-
});
|
|
6157
|
+
return createOpenAIBackend(def);
|
|
5990
6158
|
case "gemini":
|
|
5991
|
-
return
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
return
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
...isArray ? { getModel: makeGetModel(def.model) } : {},
|
|
6001
|
-
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {},
|
|
6002
|
-
...def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}
|
|
6003
|
-
});
|
|
6004
|
-
}
|
|
6005
|
-
case "pi": {
|
|
6006
|
-
const isArray = Array.isArray(def.model);
|
|
6007
|
-
return new PiBackend({
|
|
6008
|
-
endpoint: def.endpoint,
|
|
6009
|
-
...typeof def.model === "string" ? { model: def.model } : {},
|
|
6010
|
-
...isArray ? { getModel: makeGetModel(def.model) } : {},
|
|
6011
|
-
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {},
|
|
6012
|
-
...def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}
|
|
6013
|
-
});
|
|
6014
|
-
}
|
|
6015
|
-
case "ssh": {
|
|
6016
|
-
return new SshBackend({
|
|
6017
|
-
host: def.host,
|
|
6018
|
-
remoteCommand: def.remoteCommand,
|
|
6019
|
-
...def.user !== void 0 ? { user: def.user } : {},
|
|
6020
|
-
...def.port !== void 0 ? { port: def.port } : {},
|
|
6021
|
-
...def.identityFile !== void 0 ? { identityFile: def.identityFile } : {},
|
|
6022
|
-
...def.sshOptions !== void 0 ? { sshOptions: def.sshOptions } : {},
|
|
6023
|
-
...def.sshBinary !== void 0 ? { sshBinary: def.sshBinary } : {}
|
|
6024
|
-
});
|
|
6025
|
-
}
|
|
6026
|
-
case "serverless": {
|
|
6027
|
-
switch (def.adapter) {
|
|
6028
|
-
case "oci":
|
|
6029
|
-
return new OciServerlessBackend({
|
|
6030
|
-
image: def.image,
|
|
6031
|
-
...def.registry !== void 0 ? { registry: def.registry } : {},
|
|
6032
|
-
...def.pullPolicy !== void 0 ? { pullPolicy: def.pullPolicy } : {},
|
|
6033
|
-
...def.envPassthrough !== void 0 ? { envPassthrough: def.envPassthrough } : {},
|
|
6034
|
-
...def.runtime !== void 0 ? { runtime: def.runtime } : {}
|
|
6035
|
-
});
|
|
6036
|
-
default: {
|
|
6037
|
-
const exhaustive = def.adapter;
|
|
6038
|
-
throw new Error(
|
|
6039
|
-
`createBackend: unknown serverless adapter ${JSON.stringify(exhaustive)}`
|
|
6040
|
-
);
|
|
6041
|
-
}
|
|
6042
|
-
}
|
|
6043
|
-
}
|
|
6159
|
+
return createGeminiBackend(def);
|
|
6160
|
+
case "local":
|
|
6161
|
+
return createLocalBackend(def);
|
|
6162
|
+
case "pi":
|
|
6163
|
+
return createPiBackend(def);
|
|
6164
|
+
case "ssh":
|
|
6165
|
+
return createSshBackend(def);
|
|
6166
|
+
case "serverless":
|
|
6167
|
+
return createServerlessBackend(def);
|
|
6044
6168
|
default: {
|
|
6045
6169
|
const exhaustive = def;
|
|
6046
6170
|
throw new Error(`createBackend: unknown backend type ${JSON.stringify(exhaustive)}`);
|
|
@@ -6511,6 +6635,96 @@ var OrchestratorBackendFactory = class {
|
|
|
6511
6635
|
}
|
|
6512
6636
|
};
|
|
6513
6637
|
|
|
6638
|
+
// src/agent/backend-resolver.ts
|
|
6639
|
+
function makeBackendResolver(backends) {
|
|
6640
|
+
return (backendName) => {
|
|
6641
|
+
const def = backends?.[backendName];
|
|
6642
|
+
return def ? createBackend(def) : null;
|
|
6643
|
+
};
|
|
6644
|
+
}
|
|
6645
|
+
|
|
6646
|
+
// src/maintenance/agent-dispatcher.ts
|
|
6647
|
+
function syntheticIssue(branch, skill) {
|
|
6648
|
+
return {
|
|
6649
|
+
id: `maintenance:${branch}`,
|
|
6650
|
+
identifier: branch,
|
|
6651
|
+
title: `Maintenance: ${skill}`,
|
|
6652
|
+
description: null,
|
|
6653
|
+
priority: null,
|
|
6654
|
+
state: "in_progress",
|
|
6655
|
+
branchName: branch,
|
|
6656
|
+
url: null,
|
|
6657
|
+
labels: ["maintenance"],
|
|
6658
|
+
blockedBy: [],
|
|
6659
|
+
spec: null,
|
|
6660
|
+
plans: [],
|
|
6661
|
+
createdAt: null,
|
|
6662
|
+
updatedAt: null,
|
|
6663
|
+
externalId: null
|
|
6664
|
+
};
|
|
6665
|
+
}
|
|
6666
|
+
function buildPrompt(skill, promptContext) {
|
|
6667
|
+
const instruction = `Run the \`${skill}\` skill to detect and fix issues in this repository. Apply the fixes and commit each logical change. If there is nothing to fix, make no commits.`;
|
|
6668
|
+
return promptContext ? `${promptContext}
|
|
6669
|
+
|
|
6670
|
+
${instruction}` : instruction;
|
|
6671
|
+
}
|
|
6672
|
+
function headOf(git2, cwd) {
|
|
6673
|
+
try {
|
|
6674
|
+
return git2(["rev-parse", "HEAD"], cwd) || null;
|
|
6675
|
+
} catch {
|
|
6676
|
+
return null;
|
|
6677
|
+
}
|
|
6678
|
+
}
|
|
6679
|
+
function createAgentDispatcher(deps) {
|
|
6680
|
+
const maxTurns = deps.maxTurns ?? 10;
|
|
6681
|
+
const dispatch = async (skill, branch, backendName, cwd, options) => {
|
|
6682
|
+
const backend = deps.resolveBackend(backendName);
|
|
6683
|
+
if (!backend) {
|
|
6684
|
+
deps.logger.warn(`Maintenance agent dispatch skipped: unknown backend "${backendName}"`, {
|
|
6685
|
+
skill,
|
|
6686
|
+
branch
|
|
6687
|
+
});
|
|
6688
|
+
return { producedCommits: false, fixed: 0 };
|
|
6689
|
+
}
|
|
6690
|
+
const before = headOf(deps.git, cwd);
|
|
6691
|
+
const runner = new AgentRunner(backend, { maxTurns });
|
|
6692
|
+
const session = runner.runSession(
|
|
6693
|
+
syntheticIssue(branch, skill),
|
|
6694
|
+
cwd,
|
|
6695
|
+
buildPrompt(skill, options?.promptContext)
|
|
6696
|
+
);
|
|
6697
|
+
let step = await session.next();
|
|
6698
|
+
while (!step.done) step = await session.next();
|
|
6699
|
+
const after = headOf(deps.git, cwd);
|
|
6700
|
+
let fixed = 0;
|
|
6701
|
+
if (after && after !== before) {
|
|
6702
|
+
if (before) {
|
|
6703
|
+
try {
|
|
6704
|
+
fixed = parseInt(deps.git(["rev-list", "--count", `${before}..${after}`], cwd), 10) || 0;
|
|
6705
|
+
} catch {
|
|
6706
|
+
fixed = 1;
|
|
6707
|
+
}
|
|
6708
|
+
} else {
|
|
6709
|
+
fixed = 1;
|
|
6710
|
+
}
|
|
6711
|
+
}
|
|
6712
|
+
const producedCommits = fixed > 0;
|
|
6713
|
+
deps.logger.info("Maintenance agent dispatch complete", {
|
|
6714
|
+
skill,
|
|
6715
|
+
branch,
|
|
6716
|
+
backendName,
|
|
6717
|
+
producedCommits,
|
|
6718
|
+
fixed
|
|
6719
|
+
});
|
|
6720
|
+
return { producedCommits, fixed };
|
|
6721
|
+
};
|
|
6722
|
+
return { dispatch };
|
|
6723
|
+
}
|
|
6724
|
+
|
|
6725
|
+
// src/orchestrator.ts
|
|
6726
|
+
var import_node_child_process13 = require("child_process");
|
|
6727
|
+
|
|
6514
6728
|
// src/agent/intelligence-factory.ts
|
|
6515
6729
|
var import_intelligence3 = require("@harness-engineering/intelligence");
|
|
6516
6730
|
var import_graph = require("@harness-engineering/graph");
|
|
@@ -7012,7 +7226,7 @@ function handleV1InteractionsResolveRoute(req, res, queue) {
|
|
|
7012
7226
|
|
|
7013
7227
|
// src/server/routes/plans.ts
|
|
7014
7228
|
var import_zod5 = require("zod");
|
|
7015
|
-
var
|
|
7229
|
+
var fs9 = __toESM(require("fs/promises"));
|
|
7016
7230
|
var path11 = __toESM(require("path"));
|
|
7017
7231
|
var PlanWriteSchema = import_zod5.z.object({
|
|
7018
7232
|
filename: import_zod5.z.string().min(1),
|
|
@@ -7041,9 +7255,9 @@ function handlePlansRoute(req, res, plansDir) {
|
|
|
7041
7255
|
);
|
|
7042
7256
|
return;
|
|
7043
7257
|
}
|
|
7044
|
-
await
|
|
7258
|
+
await fs9.mkdir(plansDir, { recursive: true });
|
|
7045
7259
|
const filePath = path11.join(plansDir, basename3);
|
|
7046
|
-
await
|
|
7260
|
+
await fs9.writeFile(filePath, parsed.content, "utf-8");
|
|
7047
7261
|
res.writeHead(201, { "Content-Type": "application/json" });
|
|
7048
7262
|
res.end(JSON.stringify({ ok: true, filename: basename3 }));
|
|
7049
7263
|
} catch {
|
|
@@ -7418,7 +7632,6 @@ function handleAnalyzeRoute(req, res, pipeline) {
|
|
|
7418
7632
|
}
|
|
7419
7633
|
|
|
7420
7634
|
// src/server/routes/roadmap-actions.ts
|
|
7421
|
-
var fs11 = __toESM(require("fs/promises"));
|
|
7422
7635
|
var path12 = __toESM(require("path"));
|
|
7423
7636
|
var import_core7 = require("@harness-engineering/core");
|
|
7424
7637
|
var import_zod8 = require("zod");
|
|
@@ -7500,13 +7713,14 @@ function handleRoadmapActionsRoute(req, res, roadmapPath) {
|
|
|
7500
7713
|
sendJSON2(res, 400, { error: "Title must not contain newlines or markdown headings" });
|
|
7501
7714
|
return;
|
|
7502
7715
|
}
|
|
7503
|
-
const
|
|
7504
|
-
const
|
|
7505
|
-
if (!
|
|
7716
|
+
const store = (0, import_core7.resolveRoadmapStoreForFile)({ roadmapPath });
|
|
7717
|
+
const loaded = await store.load();
|
|
7718
|
+
if (!loaded.ok) {
|
|
7506
7719
|
sendJSON2(res, 500, { error: "Failed to parse roadmap file" });
|
|
7507
7720
|
return;
|
|
7508
7721
|
}
|
|
7509
|
-
const roadmap =
|
|
7722
|
+
const roadmap = loaded.value;
|
|
7723
|
+
const before = structuredClone(roadmap);
|
|
7510
7724
|
let backlog = roadmap.milestones.find((m) => m.isBacklog);
|
|
7511
7725
|
if (!backlog) {
|
|
7512
7726
|
backlog = { name: "Backlog", isBacklog: true, features: [] };
|
|
@@ -7529,10 +7743,11 @@ function handleRoadmapActionsRoute(req, res, roadmapPath) {
|
|
|
7529
7743
|
updatedAt: null
|
|
7530
7744
|
});
|
|
7531
7745
|
roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
7532
|
-
const
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7746
|
+
const persisted = await (0, import_core7.applyRoadmapDiff)(store, before, roadmap);
|
|
7747
|
+
if (!persisted.ok) {
|
|
7748
|
+
sendJSON2(res, 500, { error: persisted.error.message });
|
|
7749
|
+
return;
|
|
7750
|
+
}
|
|
7536
7751
|
sendJSON2(res, 201, { ok: true, featureName: parsed.title });
|
|
7537
7752
|
} catch (err) {
|
|
7538
7753
|
const msg = err instanceof Error ? err.message : "Failed to append to roadmap";
|
|
@@ -7798,7 +8013,6 @@ function handleV1JobsMaintenanceRoute(req, res, deps) {
|
|
|
7798
8013
|
}
|
|
7799
8014
|
|
|
7800
8015
|
// src/server/routes/v1/events-sse.ts
|
|
7801
|
-
var import_node_crypto8 = require("crypto");
|
|
7802
8016
|
var SSE_TOPICS = [
|
|
7803
8017
|
"state_change",
|
|
7804
8018
|
"agent_event",
|
|
@@ -7814,8 +8028,55 @@ var SSE_TOPICS = [
|
|
|
7814
8028
|
"webhook.subscription.deleted"
|
|
7815
8029
|
];
|
|
7816
8030
|
var HEARTBEAT_MS = 15e3;
|
|
7817
|
-
|
|
7818
|
-
|
|
8031
|
+
var DEFAULT_REPLAY_BUFFER = 1024;
|
|
8032
|
+
var SseEventLog = class {
|
|
8033
|
+
seq = 0;
|
|
8034
|
+
buffer = [];
|
|
8035
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
8036
|
+
constructor(bus, cap = DEFAULT_REPLAY_BUFFER) {
|
|
8037
|
+
this.cap = cap;
|
|
8038
|
+
for (const topic of SSE_TOPICS) {
|
|
8039
|
+
bus.on(topic, (data) => this.record(topic, data));
|
|
8040
|
+
}
|
|
8041
|
+
}
|
|
8042
|
+
cap;
|
|
8043
|
+
record(topic, data) {
|
|
8044
|
+
const event = { id: ++this.seq, topic, data };
|
|
8045
|
+
this.buffer.push(event);
|
|
8046
|
+
if (this.buffer.length > this.cap) this.buffer.shift();
|
|
8047
|
+
for (const fn of this.subscribers) fn(event);
|
|
8048
|
+
}
|
|
8049
|
+
/** Highest assigned id (0 when nothing has been recorded yet). */
|
|
8050
|
+
currentSeq() {
|
|
8051
|
+
return this.seq;
|
|
8052
|
+
}
|
|
8053
|
+
/** Buffered events strictly newer than `lastId`, oldest-first. */
|
|
8054
|
+
replayFrom(lastId) {
|
|
8055
|
+
return this.buffer.filter((e) => e.id > lastId);
|
|
8056
|
+
}
|
|
8057
|
+
/** Register a live listener; returns an unsubscribe function. */
|
|
8058
|
+
subscribe(fn) {
|
|
8059
|
+
this.subscribers.add(fn);
|
|
8060
|
+
return () => {
|
|
8061
|
+
this.subscribers.delete(fn);
|
|
8062
|
+
};
|
|
8063
|
+
}
|
|
8064
|
+
};
|
|
8065
|
+
var logsByBus = /* @__PURE__ */ new WeakMap();
|
|
8066
|
+
function getSseEventLog(bus) {
|
|
8067
|
+
let log = logsByBus.get(bus);
|
|
8068
|
+
if (!log) {
|
|
8069
|
+
log = new SseEventLog(bus);
|
|
8070
|
+
logsByBus.set(bus, log);
|
|
8071
|
+
}
|
|
8072
|
+
return log;
|
|
8073
|
+
}
|
|
8074
|
+
function parseLastEventId(req) {
|
|
8075
|
+
const raw = req.headers["last-event-id"];
|
|
8076
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
8077
|
+
if (value === void 0) return null;
|
|
8078
|
+
const n = Number(value);
|
|
8079
|
+
return Number.isInteger(n) && n >= 0 ? n : null;
|
|
7819
8080
|
}
|
|
7820
8081
|
function handleV1EventsSseRoute(req, res, bus) {
|
|
7821
8082
|
if (req.method !== "GET" || req.url !== "/api/v1/events") return false;
|
|
@@ -7827,22 +8088,28 @@ function handleV1EventsSseRoute(req, res, bus) {
|
|
|
7827
8088
|
res.write(`: harness gateway SSE \u2014 connected at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
7828
8089
|
|
|
7829
8090
|
`);
|
|
7830
|
-
const
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
id: ${newEventId()}
|
|
8091
|
+
const log = getSseEventLog(bus);
|
|
8092
|
+
const send = (e) => {
|
|
8093
|
+
try {
|
|
8094
|
+
res.write(`event: ${e.topic}
|
|
8095
|
+
data: ${JSON.stringify(e.data)}
|
|
8096
|
+
id: ${e.id}
|
|
7837
8097
|
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
8098
|
+
`);
|
|
8099
|
+
} catch {
|
|
8100
|
+
}
|
|
8101
|
+
};
|
|
8102
|
+
const lastId = parseLastEventId(req);
|
|
8103
|
+
let replayedThrough = lastId ?? log.currentSeq();
|
|
8104
|
+
if (lastId !== null) {
|
|
8105
|
+
for (const e of log.replayFrom(lastId)) {
|
|
8106
|
+
send(e);
|
|
8107
|
+
replayedThrough = e.id;
|
|
8108
|
+
}
|
|
7845
8109
|
}
|
|
8110
|
+
const unsubscribe = log.subscribe((e) => {
|
|
8111
|
+
if (e.id > replayedThrough) send(e);
|
|
8112
|
+
});
|
|
7846
8113
|
const heartbeat = setInterval(() => {
|
|
7847
8114
|
try {
|
|
7848
8115
|
res.write(": heartbeat\n\n");
|
|
@@ -7852,7 +8119,7 @@ id: ${newEventId()}
|
|
|
7852
8119
|
heartbeat.unref();
|
|
7853
8120
|
const cleanup = () => {
|
|
7854
8121
|
clearInterval(heartbeat);
|
|
7855
|
-
|
|
8122
|
+
unsubscribe();
|
|
7856
8123
|
};
|
|
7857
8124
|
res.on("close", cleanup);
|
|
7858
8125
|
res.on("finish", cleanup);
|
|
@@ -8161,7 +8428,7 @@ async function runGate(projectPath, proposalId) {
|
|
|
8161
8428
|
}
|
|
8162
8429
|
|
|
8163
8430
|
// src/proposals/promote.ts
|
|
8164
|
-
var
|
|
8431
|
+
var fs10 = __toESM(require("fs"));
|
|
8165
8432
|
var path13 = __toESM(require("path"));
|
|
8166
8433
|
var import_yaml4 = require("yaml");
|
|
8167
8434
|
var import_core9 = require("@harness-engineering/core");
|
|
@@ -8183,7 +8450,7 @@ function skillDir(projectPath, name) {
|
|
|
8183
8450
|
}
|
|
8184
8451
|
function readIfExists(p) {
|
|
8185
8452
|
try {
|
|
8186
|
-
return
|
|
8453
|
+
return fs10.readFileSync(p, "utf-8");
|
|
8187
8454
|
} catch {
|
|
8188
8455
|
return null;
|
|
8189
8456
|
}
|
|
@@ -8229,15 +8496,15 @@ function assertGateReady(proposal) {
|
|
|
8229
8496
|
}
|
|
8230
8497
|
async function promoteNewSkill(projectPath, proposal) {
|
|
8231
8498
|
const target = skillDir(projectPath, proposal.content.name);
|
|
8232
|
-
if (
|
|
8499
|
+
if (fs10.existsSync(target)) {
|
|
8233
8500
|
throw new PromotionError(
|
|
8234
8501
|
`a catalog skill already exists at ${target}; use a refinement proposal to update it`
|
|
8235
8502
|
);
|
|
8236
8503
|
}
|
|
8237
|
-
|
|
8504
|
+
fs10.mkdirSync(target, { recursive: true });
|
|
8238
8505
|
const yamlOut = injectProvenanceIntoYaml(proposal.content.skillYaml ?? "", proposal.id);
|
|
8239
|
-
|
|
8240
|
-
|
|
8506
|
+
fs10.writeFileSync(path13.join(target, "skill.yaml"), yamlOut);
|
|
8507
|
+
fs10.writeFileSync(path13.join(target, "SKILL.md"), proposal.content.skillMd ?? "");
|
|
8241
8508
|
return { skillPath: target };
|
|
8242
8509
|
}
|
|
8243
8510
|
async function promoteRefinement(projectPath, proposal) {
|
|
@@ -8245,7 +8512,7 @@ async function promoteRefinement(projectPath, proposal) {
|
|
|
8245
8512
|
throw new PromotionError("refinement proposal is missing targetSkill");
|
|
8246
8513
|
}
|
|
8247
8514
|
const target = skillDir(projectPath, proposal.targetSkill);
|
|
8248
|
-
if (!
|
|
8515
|
+
if (!fs10.existsSync(target)) {
|
|
8249
8516
|
throw new PromotionError(
|
|
8250
8517
|
`target skill ${proposal.targetSkill} does not exist at ${target}; cannot refine`
|
|
8251
8518
|
);
|
|
@@ -8258,7 +8525,7 @@ async function promoteRefinement(projectPath, proposal) {
|
|
|
8258
8525
|
"no metadata changes detected; check that the reviewer applied the proposed diff before approving"
|
|
8259
8526
|
);
|
|
8260
8527
|
}
|
|
8261
|
-
|
|
8528
|
+
fs10.writeFileSync(yamlPath, after);
|
|
8262
8529
|
return { skillPath: target };
|
|
8263
8530
|
}
|
|
8264
8531
|
async function promote(projectPath, proposalId, decidedBy) {
|
|
@@ -8696,7 +8963,7 @@ function handleV1RoutingRoute(req, res, deps) {
|
|
|
8696
8963
|
}
|
|
8697
8964
|
|
|
8698
8965
|
// src/server/routes/sessions.ts
|
|
8699
|
-
var
|
|
8966
|
+
var fs11 = __toESM(require("fs/promises"));
|
|
8700
8967
|
var path14 = __toESM(require("path"));
|
|
8701
8968
|
var import_zod15 = require("zod");
|
|
8702
8969
|
var SessionCreateSchema = import_zod15.z.object({
|
|
@@ -8717,12 +8984,12 @@ function extractSessionId(url) {
|
|
|
8717
8984
|
}
|
|
8718
8985
|
async function handleList2(res, sessionsDir) {
|
|
8719
8986
|
try {
|
|
8720
|
-
const entries = await
|
|
8987
|
+
const entries = await fs11.readdir(sessionsDir, { withFileTypes: true });
|
|
8721
8988
|
const sessions = [];
|
|
8722
8989
|
for (const entry of entries) {
|
|
8723
8990
|
if (!entry.isDirectory()) continue;
|
|
8724
8991
|
try {
|
|
8725
|
-
const content = await
|
|
8992
|
+
const content = await fs11.readFile(
|
|
8726
8993
|
path14.join(sessionsDir, entry.name, "session.json"),
|
|
8727
8994
|
"utf-8"
|
|
8728
8995
|
);
|
|
@@ -8748,7 +9015,7 @@ async function handleGet2(res, id, sessionsDir) {
|
|
|
8748
9015
|
return;
|
|
8749
9016
|
}
|
|
8750
9017
|
try {
|
|
8751
|
-
const content = await
|
|
9018
|
+
const content = await fs11.readFile(path14.join(sessionsDir, id, "session.json"), "utf-8");
|
|
8752
9019
|
jsonResponse(res, 200, JSON.parse(content));
|
|
8753
9020
|
} catch (err) {
|
|
8754
9021
|
if (err.code === "ENOENT") {
|
|
@@ -8772,8 +9039,8 @@ async function handleCreate(req, res, sessionsDir) {
|
|
|
8772
9039
|
return;
|
|
8773
9040
|
}
|
|
8774
9041
|
const sessionDir = path14.join(sessionsDir, session.sessionId);
|
|
8775
|
-
await
|
|
8776
|
-
await
|
|
9042
|
+
await fs11.mkdir(sessionDir, { recursive: true });
|
|
9043
|
+
await fs11.writeFile(path14.join(sessionDir, "session.json"), JSON.stringify(session, null, 2));
|
|
8777
9044
|
jsonResponse(res, 200, { ok: true });
|
|
8778
9045
|
} catch {
|
|
8779
9046
|
jsonResponse(res, 500, { error: "Failed to save session" });
|
|
@@ -8789,8 +9056,8 @@ async function handleUpdate(req, res, url, sessionsDir) {
|
|
|
8789
9056
|
const body = await readBody(req);
|
|
8790
9057
|
const updates = import_zod15.z.record(import_zod15.z.unknown()).parse(JSON.parse(body));
|
|
8791
9058
|
const sessionFilePath = path14.join(sessionsDir, id, "session.json");
|
|
8792
|
-
const current = JSON.parse(await
|
|
8793
|
-
await
|
|
9059
|
+
const current = JSON.parse(await fs11.readFile(sessionFilePath, "utf-8"));
|
|
9060
|
+
await fs11.writeFile(sessionFilePath, JSON.stringify({ ...current, ...updates }, null, 2));
|
|
8794
9061
|
jsonResponse(res, 200, { ok: true });
|
|
8795
9062
|
} catch {
|
|
8796
9063
|
jsonResponse(res, 500, { error: "Failed to update session" });
|
|
@@ -8803,7 +9070,7 @@ async function handleDelete(res, url, sessionsDir) {
|
|
|
8803
9070
|
jsonResponse(res, 400, { error: "Missing or invalid sessionId" });
|
|
8804
9071
|
return;
|
|
8805
9072
|
}
|
|
8806
|
-
await
|
|
9073
|
+
await fs11.rm(path14.join(sessionsDir, id), { recursive: true, force: true });
|
|
8807
9074
|
jsonResponse(res, 200, { ok: true });
|
|
8808
9075
|
} catch {
|
|
8809
9076
|
jsonResponse(res, 500, { error: "Failed to delete session" });
|
|
@@ -9047,7 +9314,7 @@ function handleLocalModelsRoute(req, res, getStatuses) {
|
|
|
9047
9314
|
}
|
|
9048
9315
|
|
|
9049
9316
|
// src/server/static.ts
|
|
9050
|
-
var
|
|
9317
|
+
var fs12 = __toESM(require("fs"));
|
|
9051
9318
|
var path15 = __toESM(require("path"));
|
|
9052
9319
|
var MIME_TYPES = {
|
|
9053
9320
|
".html": "text/html; charset=utf-8",
|
|
@@ -9077,11 +9344,11 @@ function handleStaticFile(req, res, dashboardDir) {
|
|
|
9077
9344
|
if (!resolved.startsWith(path15.resolve(dashboardDir))) {
|
|
9078
9345
|
return serveFile(path15.join(dashboardDir, "index.html"), res);
|
|
9079
9346
|
}
|
|
9080
|
-
if (
|
|
9347
|
+
if (fs12.existsSync(resolved) && fs12.statSync(resolved).isFile()) {
|
|
9081
9348
|
return serveFile(resolved, res);
|
|
9082
9349
|
}
|
|
9083
9350
|
const indexPath = path15.join(dashboardDir, "index.html");
|
|
9084
|
-
if (
|
|
9351
|
+
if (fs12.existsSync(indexPath)) {
|
|
9085
9352
|
return serveFile(indexPath, res);
|
|
9086
9353
|
}
|
|
9087
9354
|
return false;
|
|
@@ -9090,7 +9357,7 @@ function serveFile(filePath, res) {
|
|
|
9090
9357
|
const ext = path15.extname(filePath).toLowerCase();
|
|
9091
9358
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
9092
9359
|
try {
|
|
9093
|
-
const content =
|
|
9360
|
+
const content = fs12.readFileSync(filePath);
|
|
9094
9361
|
res.writeHead(200, { "Content-Type": contentType });
|
|
9095
9362
|
res.end(content);
|
|
9096
9363
|
return true;
|
|
@@ -9100,7 +9367,7 @@ function serveFile(filePath, res) {
|
|
|
9100
9367
|
}
|
|
9101
9368
|
|
|
9102
9369
|
// src/server/plan-watcher.ts
|
|
9103
|
-
var
|
|
9370
|
+
var fs13 = __toESM(require("fs"));
|
|
9104
9371
|
var path16 = __toESM(require("path"));
|
|
9105
9372
|
var PlanWatcher = class {
|
|
9106
9373
|
plansDir;
|
|
@@ -9115,11 +9382,11 @@ var PlanWatcher = class {
|
|
|
9115
9382
|
* Creates the directory if it does not exist.
|
|
9116
9383
|
*/
|
|
9117
9384
|
start() {
|
|
9118
|
-
|
|
9119
|
-
this.watcher =
|
|
9385
|
+
fs13.mkdirSync(this.plansDir, { recursive: true });
|
|
9386
|
+
this.watcher = fs13.watch(this.plansDir, (eventType, filename) => {
|
|
9120
9387
|
if (eventType === "rename" && filename && filename.endsWith(".md")) {
|
|
9121
9388
|
const filePath = path16.join(this.plansDir, filename);
|
|
9122
|
-
if (
|
|
9389
|
+
if (fs13.existsSync(filePath)) {
|
|
9123
9390
|
void this.handleNewPlan(filename);
|
|
9124
9391
|
}
|
|
9125
9392
|
}
|
|
@@ -9150,7 +9417,7 @@ var PlanWatcher = class {
|
|
|
9150
9417
|
};
|
|
9151
9418
|
|
|
9152
9419
|
// src/auth/tokens.ts
|
|
9153
|
-
var
|
|
9420
|
+
var import_node_crypto8 = require("crypto");
|
|
9154
9421
|
var import_promises = require("fs/promises");
|
|
9155
9422
|
var import_node_path = require("path");
|
|
9156
9423
|
var import_bcryptjs = __toESM(require("bcryptjs"));
|
|
@@ -9158,10 +9425,10 @@ var import_types26 = require("@harness-engineering/types");
|
|
|
9158
9425
|
var BCRYPT_ROUNDS = 12;
|
|
9159
9426
|
var LEGACY_ENV_ID = "tok_legacy_env";
|
|
9160
9427
|
function genId() {
|
|
9161
|
-
return `tok_${(0,
|
|
9428
|
+
return `tok_${(0, import_node_crypto8.randomBytes)(8).toString("hex")}`;
|
|
9162
9429
|
}
|
|
9163
9430
|
function genSecret() {
|
|
9164
|
-
return (0,
|
|
9431
|
+
return (0, import_node_crypto8.randomBytes)(24).toString("base64url");
|
|
9165
9432
|
}
|
|
9166
9433
|
function parseToken(raw) {
|
|
9167
9434
|
const dot = raw.indexOf(".");
|
|
@@ -9192,7 +9459,7 @@ var TokenStore = class {
|
|
|
9192
9459
|
}
|
|
9193
9460
|
async persist(records) {
|
|
9194
9461
|
await (0, import_promises.mkdir)((0, import_node_path.dirname)(this.path), { recursive: true });
|
|
9195
|
-
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${(0,
|
|
9462
|
+
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${(0, import_node_crypto8.randomBytes)(4).toString("hex")}`;
|
|
9196
9463
|
await (0, import_promises.writeFile)(tmp, JSON.stringify(records, null, 2), "utf8");
|
|
9197
9464
|
await (0, import_promises.rename)(tmp, this.path);
|
|
9198
9465
|
this.cache = records;
|
|
@@ -9261,7 +9528,7 @@ var TokenStore = class {
|
|
|
9261
9528
|
const a = Buffer.from(presented);
|
|
9262
9529
|
const b = Buffer.from(envValue);
|
|
9263
9530
|
if (a.length !== b.length) return null;
|
|
9264
|
-
if (!(0,
|
|
9531
|
+
if (!(0, import_node_crypto8.timingSafeEqual)(a, b)) return null;
|
|
9265
9532
|
return {
|
|
9266
9533
|
id: LEGACY_ENV_ID,
|
|
9267
9534
|
name: "legacy-env",
|
|
@@ -9444,26 +9711,41 @@ function hasScope(held, required) {
|
|
|
9444
9711
|
if (held.includes("admin")) return true;
|
|
9445
9712
|
return held.includes(required);
|
|
9446
9713
|
}
|
|
9447
|
-
function
|
|
9448
|
-
const bridgeScope = requiredBridgeScope(method, path24);
|
|
9449
|
-
if (bridgeScope) return bridgeScope;
|
|
9714
|
+
function exactScopeForRoute(method, path24) {
|
|
9450
9715
|
if (path24 === "/api/v1/auth/token" && method === "POST") return "admin";
|
|
9451
9716
|
if (path24 === "/api/v1/auth/tokens" && method === "GET") return "admin";
|
|
9452
9717
|
if (/^\/api\/v1\/auth\/tokens\/[^/]+$/.test(path24) && method === "DELETE") return "admin";
|
|
9453
9718
|
if ((path24 === "/api/state" || path24 === "/api/v1/state") && method === "GET") return "read-status";
|
|
9454
|
-
if (path24.startsWith("/api/interactions")) return "resolve-interaction";
|
|
9455
|
-
if (path24.startsWith("/api/plans")) return "read-status";
|
|
9456
|
-
if (path24.startsWith("/api/analyze") || path24.startsWith("/api/analyses")) return "read-status";
|
|
9457
|
-
if (path24.startsWith("/api/roadmap-actions")) return "modify-roadmap";
|
|
9458
|
-
if (path24.startsWith("/api/dispatch-actions")) return "trigger-job";
|
|
9459
|
-
if (path24.startsWith("/api/local-model") || path24.startsWith("/api/local-models"))
|
|
9460
|
-
return "read-status";
|
|
9461
|
-
if (path24.startsWith("/api/maintenance")) return "trigger-job";
|
|
9462
|
-
if (path24.startsWith("/api/streams")) return "read-status";
|
|
9463
|
-
if (path24.startsWith("/api/sessions")) return "read-status";
|
|
9464
|
-
if (path24 === "/api/chat" || path24.startsWith("/api/chat-proxy")) return "trigger-job";
|
|
9465
9719
|
return null;
|
|
9466
9720
|
}
|
|
9721
|
+
var PREFIX_SCOPES = [
|
|
9722
|
+
["/api/interactions", "resolve-interaction"],
|
|
9723
|
+
["/api/plans", "read-status"],
|
|
9724
|
+
["/api/analyze", "read-status"],
|
|
9725
|
+
["/api/analyses", "read-status"],
|
|
9726
|
+
["/api/roadmap-actions", "modify-roadmap"],
|
|
9727
|
+
["/api/dispatch-actions", "trigger-job"],
|
|
9728
|
+
["/api/local-model", "read-status"],
|
|
9729
|
+
["/api/local-models", "read-status"],
|
|
9730
|
+
["/api/maintenance", "trigger-job"],
|
|
9731
|
+
["/api/streams", "read-status"],
|
|
9732
|
+
["/api/sessions", "read-status"],
|
|
9733
|
+
["/api/chat-proxy", "trigger-job"]
|
|
9734
|
+
];
|
|
9735
|
+
function prefixScopeForPath(path24) {
|
|
9736
|
+
if (path24 === "/api/chat") return "trigger-job";
|
|
9737
|
+
for (const [prefix, scope] of PREFIX_SCOPES) {
|
|
9738
|
+
if (path24.startsWith(prefix)) return scope;
|
|
9739
|
+
}
|
|
9740
|
+
return null;
|
|
9741
|
+
}
|
|
9742
|
+
function requiredScopeForRoute(method, path24) {
|
|
9743
|
+
const bridgeScope = requiredBridgeScope(method, path24);
|
|
9744
|
+
if (bridgeScope) return bridgeScope;
|
|
9745
|
+
const exactScope = exactScopeForRoute(method, path24);
|
|
9746
|
+
if (exactScope) return exactScope;
|
|
9747
|
+
return prefixScopeForPath(path24);
|
|
9748
|
+
}
|
|
9467
9749
|
|
|
9468
9750
|
// src/server/http.ts
|
|
9469
9751
|
var RATE_LIMIT = Number(process.env["HARNESS_RATE_LIMIT"]) || 100;
|
|
@@ -9878,15 +10160,15 @@ var OrchestratorServer = class {
|
|
|
9878
10160
|
};
|
|
9879
10161
|
|
|
9880
10162
|
// src/gateway/webhooks/store.ts
|
|
9881
|
-
var
|
|
10163
|
+
var import_node_crypto10 = require("crypto");
|
|
9882
10164
|
var import_promises3 = require("fs/promises");
|
|
9883
10165
|
var import_node_path3 = require("path");
|
|
9884
10166
|
var import_types28 = require("@harness-engineering/types");
|
|
9885
10167
|
|
|
9886
10168
|
// src/gateway/webhooks/signer.ts
|
|
9887
|
-
var
|
|
10169
|
+
var import_node_crypto9 = require("crypto");
|
|
9888
10170
|
function sign(secret, body) {
|
|
9889
|
-
const hex = (0,
|
|
10171
|
+
const hex = (0, import_node_crypto9.createHmac)("sha256", secret).update(body).digest("hex");
|
|
9890
10172
|
return `sha256=${hex}`;
|
|
9891
10173
|
}
|
|
9892
10174
|
function eventMatches(pattern, type) {
|
|
@@ -9905,10 +10187,10 @@ function eventMatches(pattern, type) {
|
|
|
9905
10187
|
|
|
9906
10188
|
// src/gateway/webhooks/store.ts
|
|
9907
10189
|
function genId2() {
|
|
9908
|
-
return `whk_${(0,
|
|
10190
|
+
return `whk_${(0, import_node_crypto10.randomBytes)(8).toString("hex")}`;
|
|
9909
10191
|
}
|
|
9910
10192
|
function genSecret2() {
|
|
9911
|
-
return (0,
|
|
10193
|
+
return (0, import_node_crypto10.randomBytes)(32).toString("base64url");
|
|
9912
10194
|
}
|
|
9913
10195
|
var WebhookStore = class {
|
|
9914
10196
|
constructor(path24) {
|
|
@@ -9933,7 +10215,7 @@ var WebhookStore = class {
|
|
|
9933
10215
|
return this.cache;
|
|
9934
10216
|
}
|
|
9935
10217
|
async persist(records) {
|
|
9936
|
-
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${(0,
|
|
10218
|
+
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${(0, import_node_crypto10.randomBytes)(4).toString("hex")}`;
|
|
9937
10219
|
try {
|
|
9938
10220
|
await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(this.path), { recursive: true });
|
|
9939
10221
|
await (0, import_promises3.writeFile)(tmp, JSON.stringify(records, null, 2), { encoding: "utf8", mode: 384 });
|
|
@@ -9977,7 +10259,7 @@ var WebhookStore = class {
|
|
|
9977
10259
|
};
|
|
9978
10260
|
|
|
9979
10261
|
// src/gateway/webhooks/delivery.ts
|
|
9980
|
-
var
|
|
10262
|
+
var import_node_crypto11 = require("crypto");
|
|
9981
10263
|
|
|
9982
10264
|
// src/gateway/webhooks/queue.ts
|
|
9983
10265
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
@@ -10187,7 +10469,7 @@ var WebhookDelivery = class {
|
|
|
10187
10469
|
enqueue(sub, event) {
|
|
10188
10470
|
const payload = JSON.stringify(event);
|
|
10189
10471
|
this.queue.insert({
|
|
10190
|
-
id: `dlv_${(0,
|
|
10472
|
+
id: `dlv_${(0, import_node_crypto11.randomBytes)(8).toString("hex")}`,
|
|
10191
10473
|
subscriptionId: sub.id,
|
|
10192
10474
|
eventType: event.type,
|
|
10193
10475
|
payload
|
|
@@ -10295,7 +10577,7 @@ var WebhookDelivery = class {
|
|
|
10295
10577
|
};
|
|
10296
10578
|
|
|
10297
10579
|
// src/gateway/webhooks/events.ts
|
|
10298
|
-
var
|
|
10580
|
+
var import_node_crypto12 = require("crypto");
|
|
10299
10581
|
var WEBHOOK_TOPICS = [
|
|
10300
10582
|
"interaction.created",
|
|
10301
10583
|
"interaction.resolved",
|
|
@@ -10310,8 +10592,8 @@ var WEBHOOK_TOPICS = [
|
|
|
10310
10592
|
"proposal.approved",
|
|
10311
10593
|
"proposal.rejected"
|
|
10312
10594
|
];
|
|
10313
|
-
function
|
|
10314
|
-
return `evt_${(0,
|
|
10595
|
+
function newEventId() {
|
|
10596
|
+
return `evt_${(0, import_node_crypto12.randomBytes)(8).toString("hex")}`;
|
|
10315
10597
|
}
|
|
10316
10598
|
function wireWebhookFanout({ bus, store, delivery }) {
|
|
10317
10599
|
const handlers = [];
|
|
@@ -10322,7 +10604,7 @@ function wireWebhookFanout({ bus, store, delivery }) {
|
|
|
10322
10604
|
const subs = await store.listForEvent(eventType);
|
|
10323
10605
|
if (subs.length === 0) return;
|
|
10324
10606
|
const event = {
|
|
10325
|
-
id:
|
|
10607
|
+
id: newEventId(),
|
|
10326
10608
|
type: eventType,
|
|
10327
10609
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10328
10610
|
data
|
|
@@ -10341,7 +10623,7 @@ function wireWebhookFanout({ bus, store, delivery }) {
|
|
|
10341
10623
|
}
|
|
10342
10624
|
|
|
10343
10625
|
// src/gateway/telemetry/fanout.ts
|
|
10344
|
-
var
|
|
10626
|
+
var import_node_crypto13 = require("crypto");
|
|
10345
10627
|
var import_core12 = require("@harness-engineering/core");
|
|
10346
10628
|
var TOPICS = {
|
|
10347
10629
|
MAINTENANCE_STARTED: "maintenance:started",
|
|
@@ -10364,14 +10646,14 @@ var SPAN_NAME = {
|
|
|
10364
10646
|
[TOPICS.DISPATCH_DECISION]: "dispatch_decision",
|
|
10365
10647
|
[TOPICS.SKILL_INVOCATION]: "skill_invocation"
|
|
10366
10648
|
};
|
|
10367
|
-
function
|
|
10368
|
-
return `evt_${(0,
|
|
10649
|
+
function newEventId2() {
|
|
10650
|
+
return `evt_${(0, import_node_crypto13.randomBytes)(8).toString("hex")}`;
|
|
10369
10651
|
}
|
|
10370
10652
|
function newTraceId() {
|
|
10371
|
-
return (0,
|
|
10653
|
+
return (0, import_node_crypto13.randomBytes)(16).toString("hex");
|
|
10372
10654
|
}
|
|
10373
10655
|
function newSpanId() {
|
|
10374
|
-
return (0,
|
|
10656
|
+
return (0, import_node_crypto13.randomBytes)(8).toString("hex");
|
|
10375
10657
|
}
|
|
10376
10658
|
function nowNs() {
|
|
10377
10659
|
return BigInt(Date.now()) * 1000000n;
|
|
@@ -10422,79 +10704,102 @@ function buildAttributes(payload, extras = {}) {
|
|
|
10422
10704
|
}
|
|
10423
10705
|
return attrs;
|
|
10424
10706
|
}
|
|
10707
|
+
function extractIds(payload) {
|
|
10708
|
+
const correlationId = typeof payload["correlationId"] === "string" ? payload["correlationId"] : void 0;
|
|
10709
|
+
const taskId = typeof payload["taskId"] === "string" ? payload["taskId"] : void 0;
|
|
10710
|
+
return { correlationId, taskId };
|
|
10711
|
+
}
|
|
10712
|
+
function resolveActiveRun(registry, correlationId, taskId) {
|
|
10713
|
+
return registry.resolve({
|
|
10714
|
+
...correlationId !== void 0 ? { correlationId } : {},
|
|
10715
|
+
...taskId !== void 0 ? { taskId } : {}
|
|
10716
|
+
});
|
|
10717
|
+
}
|
|
10718
|
+
function openMaintenanceSpan(registry, correlationId, taskId) {
|
|
10719
|
+
const traceId = newTraceId();
|
|
10720
|
+
const spanId = newSpanId();
|
|
10721
|
+
const key = correlationId ?? taskId ?? `run_${spanId}`;
|
|
10722
|
+
registry.open(key, { traceId, spanId });
|
|
10723
|
+
return { traceId, spanId };
|
|
10724
|
+
}
|
|
10725
|
+
function closeMaintenanceSpan(registry, topic, correlationId, taskId) {
|
|
10726
|
+
const existing = resolveActiveRun(registry, correlationId, taskId);
|
|
10727
|
+
const traceId = existing?.traceId ?? newTraceId();
|
|
10728
|
+
const spanId = existing?.spanId ?? newSpanId();
|
|
10729
|
+
const statusCode = topic === TOPICS.MAINTENANCE_ERROR ? 2 : 1;
|
|
10730
|
+
const key = correlationId ?? taskId ?? "";
|
|
10731
|
+
if (key) registry.close(key);
|
|
10732
|
+
return { traceId, spanId, statusCode };
|
|
10733
|
+
}
|
|
10734
|
+
function childSpan(registry, correlationId, taskId) {
|
|
10735
|
+
const parent = resolveActiveRun(registry, correlationId, taskId);
|
|
10736
|
+
const plan = {
|
|
10737
|
+
traceId: parent?.traceId ?? newTraceId(),
|
|
10738
|
+
spanId: newSpanId()
|
|
10739
|
+
};
|
|
10740
|
+
if (parent?.spanId !== void 0) plan.parentSpanId = parent.spanId;
|
|
10741
|
+
return plan;
|
|
10742
|
+
}
|
|
10743
|
+
function planSpan(registry, topic, correlationId, taskId) {
|
|
10744
|
+
if (topic === TOPICS.MAINTENANCE_STARTED) {
|
|
10745
|
+
return openMaintenanceSpan(registry, correlationId, taskId);
|
|
10746
|
+
}
|
|
10747
|
+
if (topic === TOPICS.MAINTENANCE_COMPLETED || topic === TOPICS.MAINTENANCE_ERROR) {
|
|
10748
|
+
return closeMaintenanceSpan(registry, topic, correlationId, taskId);
|
|
10749
|
+
}
|
|
10750
|
+
return childSpan(registry, correlationId, taskId);
|
|
10751
|
+
}
|
|
10752
|
+
function buildSpan(topic, plan, payload) {
|
|
10753
|
+
const startNs = nowNs();
|
|
10754
|
+
return {
|
|
10755
|
+
traceId: plan.traceId,
|
|
10756
|
+
spanId: plan.spanId,
|
|
10757
|
+
...plan.parentSpanId !== void 0 ? { parentSpanId: plan.parentSpanId } : {},
|
|
10758
|
+
name: SPAN_NAME[topic],
|
|
10759
|
+
kind: import_core12.SpanKind.INTERNAL,
|
|
10760
|
+
startTimeNs: startNs,
|
|
10761
|
+
endTimeNs: startNs,
|
|
10762
|
+
attributes: buildAttributes(payload, { "harness.topic": topic }),
|
|
10763
|
+
...plan.statusCode !== void 0 ? { statusCode: plan.statusCode } : {}
|
|
10764
|
+
};
|
|
10765
|
+
}
|
|
10766
|
+
function buildGatewayEvent(topic, payload, correlationId) {
|
|
10767
|
+
return {
|
|
10768
|
+
id: newEventId2(),
|
|
10769
|
+
type: TELEMETRY_TYPE[topic],
|
|
10770
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10771
|
+
data: payload,
|
|
10772
|
+
...correlationId !== void 0 ? { correlationId } : {}
|
|
10773
|
+
};
|
|
10774
|
+
}
|
|
10775
|
+
async function enqueueToMatchingSubs(store, webhookDelivery, event) {
|
|
10776
|
+
const subs = await store.listForEvent(event.type);
|
|
10777
|
+
if (subs.length === 0) return;
|
|
10778
|
+
const filtered = subs.filter((sub) => sub.events.some((p) => eventMatches(p, event.type)));
|
|
10779
|
+
for (const sub of filtered) {
|
|
10780
|
+
webhookDelivery.enqueue(sub, event);
|
|
10781
|
+
}
|
|
10782
|
+
}
|
|
10783
|
+
function makeTelemetryHandler(ctx, topic) {
|
|
10784
|
+
return (data) => {
|
|
10785
|
+
const payload = data ?? {};
|
|
10786
|
+
const { correlationId, taskId } = extractIds(payload);
|
|
10787
|
+
const plan = planSpan(ctx.registry, topic, correlationId, taskId);
|
|
10788
|
+
ctx.exporter.push(buildSpan(topic, plan, payload));
|
|
10789
|
+
void enqueueToMatchingSubs(
|
|
10790
|
+
ctx.store,
|
|
10791
|
+
ctx.webhookDelivery,
|
|
10792
|
+
buildGatewayEvent(topic, payload, correlationId)
|
|
10793
|
+
);
|
|
10794
|
+
};
|
|
10795
|
+
}
|
|
10425
10796
|
function wireTelemetryFanout(params) {
|
|
10426
10797
|
const { bus, exporter, webhookDelivery, store } = params;
|
|
10427
10798
|
const registry = new ActiveRunRegistry();
|
|
10428
10799
|
const handlers = [];
|
|
10429
|
-
const
|
|
10430
|
-
const subs = await store.listForEvent(event.type);
|
|
10431
|
-
if (subs.length === 0) return;
|
|
10432
|
-
const filtered = subs.filter((sub) => sub.events.some((p) => eventMatches(p, event.type)));
|
|
10433
|
-
for (const sub of filtered) {
|
|
10434
|
-
webhookDelivery.enqueue(sub, event);
|
|
10435
|
-
}
|
|
10436
|
-
};
|
|
10437
|
-
const makeHandler = (topic) => (data) => {
|
|
10438
|
-
const payload = data ?? {};
|
|
10439
|
-
const correlationId = typeof payload["correlationId"] === "string" ? payload["correlationId"] : void 0;
|
|
10440
|
-
const taskId = typeof payload["taskId"] === "string" ? payload["taskId"] : void 0;
|
|
10441
|
-
let traceId;
|
|
10442
|
-
let spanId;
|
|
10443
|
-
let parentSpanId;
|
|
10444
|
-
let statusCode;
|
|
10445
|
-
if (topic === TOPICS.MAINTENANCE_STARTED) {
|
|
10446
|
-
traceId = newTraceId();
|
|
10447
|
-
spanId = newSpanId();
|
|
10448
|
-
const key = correlationId ?? taskId ?? `run_${spanId}`;
|
|
10449
|
-
registry.open(key, { traceId, spanId });
|
|
10450
|
-
} else if (topic === TOPICS.MAINTENANCE_COMPLETED || topic === TOPICS.MAINTENANCE_ERROR) {
|
|
10451
|
-
const existing = registry.resolve({
|
|
10452
|
-
...correlationId !== void 0 ? { correlationId } : {},
|
|
10453
|
-
...taskId !== void 0 ? { taskId } : {}
|
|
10454
|
-
});
|
|
10455
|
-
if (existing !== void 0) {
|
|
10456
|
-
traceId = existing.traceId;
|
|
10457
|
-
spanId = existing.spanId;
|
|
10458
|
-
} else {
|
|
10459
|
-
traceId = newTraceId();
|
|
10460
|
-
spanId = newSpanId();
|
|
10461
|
-
}
|
|
10462
|
-
statusCode = topic === TOPICS.MAINTENANCE_ERROR ? 2 : 1;
|
|
10463
|
-
const key = correlationId ?? taskId ?? "";
|
|
10464
|
-
if (key) registry.close(key);
|
|
10465
|
-
} else {
|
|
10466
|
-
const parent = registry.resolve({
|
|
10467
|
-
...correlationId !== void 0 ? { correlationId } : {},
|
|
10468
|
-
...taskId !== void 0 ? { taskId } : {}
|
|
10469
|
-
});
|
|
10470
|
-
traceId = parent?.traceId ?? newTraceId();
|
|
10471
|
-
spanId = newSpanId();
|
|
10472
|
-
parentSpanId = parent?.spanId;
|
|
10473
|
-
}
|
|
10474
|
-
const startNs = nowNs();
|
|
10475
|
-
const span = {
|
|
10476
|
-
traceId,
|
|
10477
|
-
spanId,
|
|
10478
|
-
...parentSpanId !== void 0 ? { parentSpanId } : {},
|
|
10479
|
-
name: SPAN_NAME[topic],
|
|
10480
|
-
kind: import_core12.SpanKind.INTERNAL,
|
|
10481
|
-
startTimeNs: startNs,
|
|
10482
|
-
endTimeNs: startNs,
|
|
10483
|
-
attributes: buildAttributes(payload, { "harness.topic": topic }),
|
|
10484
|
-
...statusCode !== void 0 ? { statusCode } : {}
|
|
10485
|
-
};
|
|
10486
|
-
exporter.push(span);
|
|
10487
|
-
const gatewayEvent = {
|
|
10488
|
-
id: newEventId3(),
|
|
10489
|
-
type: TELEMETRY_TYPE[topic],
|
|
10490
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10491
|
-
data: payload,
|
|
10492
|
-
...correlationId !== void 0 ? { correlationId } : {}
|
|
10493
|
-
};
|
|
10494
|
-
void enqueueToMatchingSubs(gatewayEvent);
|
|
10495
|
-
};
|
|
10800
|
+
const ctx = { registry, exporter, webhookDelivery, store };
|
|
10496
10801
|
for (const topic of Object.values(TOPICS)) {
|
|
10497
|
-
const fn =
|
|
10802
|
+
const fn = makeTelemetryHandler(ctx, topic);
|
|
10498
10803
|
bus.on(topic, fn);
|
|
10499
10804
|
handlers.push({ topic, fn });
|
|
10500
10805
|
}
|
|
@@ -10679,7 +10984,7 @@ function buildSlackSink(config, options) {
|
|
|
10679
10984
|
}
|
|
10680
10985
|
|
|
10681
10986
|
// src/notifications/events.ts
|
|
10682
|
-
var
|
|
10987
|
+
var import_node_crypto14 = require("crypto");
|
|
10683
10988
|
|
|
10684
10989
|
// src/notifications/envelope.ts
|
|
10685
10990
|
function asObj(data) {
|
|
@@ -10810,8 +11115,8 @@ var NOTIFICATION_TOPICS = [
|
|
|
10810
11115
|
"proposal.approved",
|
|
10811
11116
|
"proposal.rejected"
|
|
10812
11117
|
];
|
|
10813
|
-
function
|
|
10814
|
-
return `evt_${(0,
|
|
11118
|
+
function newEventId3() {
|
|
11119
|
+
return `evt_${(0, import_node_crypto14.randomBytes)(8).toString("hex")}`;
|
|
10815
11120
|
}
|
|
10816
11121
|
function dispatchToEntry(bus, entry, event) {
|
|
10817
11122
|
const eventType = event.type;
|
|
@@ -10846,7 +11151,7 @@ function wireNotificationSinks({ bus, registry }) {
|
|
|
10846
11151
|
const entries = registry.list();
|
|
10847
11152
|
if (entries.length === 0) return;
|
|
10848
11153
|
const event = {
|
|
10849
|
-
id:
|
|
11154
|
+
id: newEventId3(),
|
|
10850
11155
|
type: eventType,
|
|
10851
11156
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10852
11157
|
data
|
|
@@ -10864,7 +11169,7 @@ function wireNotificationSinks({ bus, registry }) {
|
|
|
10864
11169
|
}
|
|
10865
11170
|
|
|
10866
11171
|
// src/orchestrator.ts
|
|
10867
|
-
var
|
|
11172
|
+
var import_core17 = require("@harness-engineering/core");
|
|
10868
11173
|
|
|
10869
11174
|
// src/logging/logger.ts
|
|
10870
11175
|
var StructuredLogger = class {
|
|
@@ -10949,6 +11254,37 @@ async function scanWorkspaceConfig(workspacePath) {
|
|
|
10949
11254
|
return { exitCode: (0, import_core13.computeScanExitCode)(results), results };
|
|
10950
11255
|
}
|
|
10951
11256
|
|
|
11257
|
+
// src/core/lane-persistence.ts
|
|
11258
|
+
var import_core14 = require("@harness-engineering/core");
|
|
11259
|
+
var import_types29 = require("@harness-engineering/types");
|
|
11260
|
+
var SIGNAL_TO_LANE = {
|
|
11261
|
+
claim: "claimed",
|
|
11262
|
+
dispatch: "in_progress",
|
|
11263
|
+
success: "in_review",
|
|
11264
|
+
failure: "blocked",
|
|
11265
|
+
abandon: "canceled"
|
|
11266
|
+
};
|
|
11267
|
+
function mapOrchestratorLane(signal) {
|
|
11268
|
+
return SIGNAL_TO_LANE[signal];
|
|
11269
|
+
}
|
|
11270
|
+
async function persistLane(projectPath, issueId, signal) {
|
|
11271
|
+
try {
|
|
11272
|
+
const reg = await import_core14.eventSourcing.registerTask(projectPath, issueId, []);
|
|
11273
|
+
if (!reg.ok) return reg;
|
|
11274
|
+
return await import_core14.eventSourcing.transitionLane(projectPath, issueId, mapOrchestratorLane(signal));
|
|
11275
|
+
} catch (err) {
|
|
11276
|
+
return (0, import_types29.Err)(err instanceof Error ? err : new Error(String(err)));
|
|
11277
|
+
}
|
|
11278
|
+
}
|
|
11279
|
+
async function readPersistedLanes(projectPath) {
|
|
11280
|
+
try {
|
|
11281
|
+
const snap = await import_core14.eventSourcing.readSnapshot(projectPath);
|
|
11282
|
+
return snap.ok ? snap.value.lanes : { tasks: {} };
|
|
11283
|
+
} catch {
|
|
11284
|
+
return { tasks: {} };
|
|
11285
|
+
}
|
|
11286
|
+
}
|
|
11287
|
+
|
|
10952
11288
|
// src/maintenance/task-registry.ts
|
|
10953
11289
|
var BUILT_IN_TASKS = [
|
|
10954
11290
|
// --- Mechanical-AI ---
|
|
@@ -11012,7 +11348,13 @@ var BUILT_IN_TASKS = [
|
|
|
11012
11348
|
description: "Detect and fix cross-check violations",
|
|
11013
11349
|
schedule: "0 6 * * 1",
|
|
11014
11350
|
branch: "harness-maint/cross-check-fixes",
|
|
11015
|
-
|
|
11351
|
+
// `harness cross-check` is a dedicated read-only CLI subcommand that surfaces
|
|
11352
|
+
// JUST cross-artifact consistency (plan→implementation coverage + staleness),
|
|
11353
|
+
// mirroring the `validate_cross_check` MCP tool's core (`runCrossCheck`)
|
|
11354
|
+
// WITHOUT running the full `harness validate` suite. It prints a parseable
|
|
11355
|
+
// `Cross-check: N issues` line and exits 0 (clean) / 1 (N issues), so the
|
|
11356
|
+
// maintenance runner reports real results instead of an honest `failure`.
|
|
11357
|
+
checkCommand: ["cross-check"],
|
|
11016
11358
|
fixSkill: "harness-cross-check-fix"
|
|
11017
11359
|
},
|
|
11018
11360
|
// --- Pure-AI ---
|
|
@@ -11071,7 +11413,10 @@ var BUILT_IN_TASKS = [
|
|
|
11071
11413
|
description: "Assess overall project health",
|
|
11072
11414
|
schedule: "0 6 * * *",
|
|
11073
11415
|
branch: null,
|
|
11074
|
-
|
|
11416
|
+
// `assess_project` is an MCP tool name, not a CLI subcommand. The CLI
|
|
11417
|
+
// composite project-health report is `harness insights` (health, entropy,
|
|
11418
|
+
// decay, attention, impact) — a read-only report that records metrics.
|
|
11419
|
+
checkCommand: ["insights"]
|
|
11075
11420
|
},
|
|
11076
11421
|
{
|
|
11077
11422
|
id: "stale-constraints",
|
|
@@ -11079,7 +11424,14 @@ var BUILT_IN_TASKS = [
|
|
|
11079
11424
|
description: "Detect stale architectural constraints",
|
|
11080
11425
|
schedule: "0 2 1 * *",
|
|
11081
11426
|
branch: null,
|
|
11082
|
-
|
|
11427
|
+
// `harness stale-constraints` is a dedicated read-only CLI subcommand that
|
|
11428
|
+
// surfaces the `detect_stale_constraints` MCP tool's core in-process. It is
|
|
11429
|
+
// precondition-gated on the knowledge graph: with no graph it emits the
|
|
11430
|
+
// "No knowledge graph found. Run `harness scan` first." signature and exits
|
|
11431
|
+
// non-zero, which the runner classifies as `skipped` (not failure). With a
|
|
11432
|
+
// graph it prints a parseable `Stale constraints: N findings` line and exits
|
|
11433
|
+
// 0 (clean) / 1 (N stale), recorded as report-only metrics.
|
|
11434
|
+
checkCommand: ["stale-constraints"]
|
|
11083
11435
|
},
|
|
11084
11436
|
{
|
|
11085
11437
|
id: "graph-refresh",
|
|
@@ -11112,7 +11464,8 @@ var BUILT_IN_TASKS = [
|
|
|
11112
11464
|
description: "Clean up stale orchestrator sessions",
|
|
11113
11465
|
schedule: "0 0 * * *",
|
|
11114
11466
|
branch: null,
|
|
11115
|
-
checkCommand: ["cleanup-sessions"]
|
|
11467
|
+
checkCommand: ["cleanup-sessions"],
|
|
11468
|
+
excludeFromHumanSweep: true
|
|
11116
11469
|
},
|
|
11117
11470
|
{
|
|
11118
11471
|
id: "perf-baselines",
|
|
@@ -11120,7 +11473,8 @@ var BUILT_IN_TASKS = [
|
|
|
11120
11473
|
description: "Update performance baselines",
|
|
11121
11474
|
schedule: "0 7 * * *",
|
|
11122
11475
|
branch: null,
|
|
11123
|
-
checkCommand: ["perf", "baselines", "update"]
|
|
11476
|
+
checkCommand: ["perf", "baselines", "update"],
|
|
11477
|
+
excludeFromHumanSweep: true
|
|
11124
11478
|
},
|
|
11125
11479
|
{
|
|
11126
11480
|
id: "main-sync",
|
|
@@ -11128,7 +11482,8 @@ var BUILT_IN_TASKS = [
|
|
|
11128
11482
|
description: "Fast-forward local default branch from origin",
|
|
11129
11483
|
schedule: "*/15 * * * *",
|
|
11130
11484
|
branch: null,
|
|
11131
|
-
checkCommand: ["harness", "sync-main", "--json"]
|
|
11485
|
+
checkCommand: ["harness", "sync-main", "--json"],
|
|
11486
|
+
excludeFromHumanSweep: true
|
|
11132
11487
|
},
|
|
11133
11488
|
// Hermes Phase 4 — one-shot backfill that stamps `provenance: user-authored`
|
|
11134
11489
|
// on every existing catalog skill. Schedule is Feb 31 (a date that never
|
|
@@ -11141,7 +11496,8 @@ var BUILT_IN_TASKS = [
|
|
|
11141
11496
|
description: "Backfill provenance: user-authored on every existing skill (one-shot, idempotent)",
|
|
11142
11497
|
schedule: "0 0 31 2 *",
|
|
11143
11498
|
branch: null,
|
|
11144
|
-
checkCommand: ["backfill-skill-provenance"]
|
|
11499
|
+
checkCommand: ["backfill-skill-provenance"],
|
|
11500
|
+
excludeFromHumanSweep: true
|
|
11145
11501
|
}
|
|
11146
11502
|
];
|
|
11147
11503
|
|
|
@@ -11208,6 +11564,17 @@ function cronMatchesNow(expression, now) {
|
|
|
11208
11564
|
const daysOfWeek = parseField(dowField, 0, 6);
|
|
11209
11565
|
return minutes.has(minute) && hours.has(hour) && daysOfMonth.has(dayOfMonth) && months.has(month) && daysOfWeek.has(dayOfWeek);
|
|
11210
11566
|
}
|
|
11567
|
+
function cronMatchesDate(expression, date) {
|
|
11568
|
+
const fields = expression.trim().split(/\s+/);
|
|
11569
|
+
if (fields.length !== 5) {
|
|
11570
|
+
throw new Error(`Invalid cron expression: expected 5 fields, got ${fields.length}`);
|
|
11571
|
+
}
|
|
11572
|
+
const [, , domField, monthField, dowField] = fields;
|
|
11573
|
+
const daysOfMonth = parseField(domField, 1, 31);
|
|
11574
|
+
const months = parseField(monthField, 1, 12);
|
|
11575
|
+
const daysOfWeek = parseField(dowField, 0, 6);
|
|
11576
|
+
return daysOfMonth.has(date.getDate()) && months.has(date.getMonth() + 1) && daysOfWeek.has(date.getDay());
|
|
11577
|
+
}
|
|
11211
11578
|
|
|
11212
11579
|
// src/maintenance/scheduler.ts
|
|
11213
11580
|
var MaintenanceScheduler = class {
|
|
@@ -11449,15 +11816,15 @@ var MaintenanceScheduler = class {
|
|
|
11449
11816
|
};
|
|
11450
11817
|
|
|
11451
11818
|
// src/maintenance/leader-elector.ts
|
|
11452
|
-
var
|
|
11819
|
+
var import_types30 = require("@harness-engineering/types");
|
|
11453
11820
|
var SingleProcessLeaderElector = class {
|
|
11454
11821
|
async electLeader() {
|
|
11455
|
-
return (0,
|
|
11822
|
+
return (0, import_types30.Ok)("claimed");
|
|
11456
11823
|
}
|
|
11457
11824
|
};
|
|
11458
11825
|
|
|
11459
11826
|
// src/maintenance/reporter.ts
|
|
11460
|
-
var
|
|
11827
|
+
var fs14 = __toESM(require("fs"));
|
|
11461
11828
|
var path18 = __toESM(require("path"));
|
|
11462
11829
|
var import_zod17 = require("zod");
|
|
11463
11830
|
var RunResultSchema = import_zod17.z.object({
|
|
@@ -11493,9 +11860,9 @@ var MaintenanceReporter = class {
|
|
|
11493
11860
|
*/
|
|
11494
11861
|
async load() {
|
|
11495
11862
|
try {
|
|
11496
|
-
await
|
|
11863
|
+
await fs14.promises.mkdir(this.persistDir, { recursive: true });
|
|
11497
11864
|
const filePath = path18.join(this.persistDir, "history.json");
|
|
11498
|
-
const data = await
|
|
11865
|
+
const data = await fs14.promises.readFile(filePath, "utf-8");
|
|
11499
11866
|
const parsed = import_zod17.z.array(RunResultSchema).safeParse(JSON.parse(data));
|
|
11500
11867
|
if (parsed.success) {
|
|
11501
11868
|
this.history = parsed.data.slice(0, MAX_HISTORY);
|
|
@@ -11529,9 +11896,9 @@ var MaintenanceReporter = class {
|
|
|
11529
11896
|
*/
|
|
11530
11897
|
async persist() {
|
|
11531
11898
|
try {
|
|
11532
|
-
await
|
|
11899
|
+
await fs14.promises.mkdir(this.persistDir, { recursive: true });
|
|
11533
11900
|
const filePath = path18.join(this.persistDir, "history.json");
|
|
11534
|
-
await
|
|
11901
|
+
await fs14.promises.writeFile(filePath, JSON.stringify(this.history, null, 2), "utf-8");
|
|
11535
11902
|
} catch (err) {
|
|
11536
11903
|
this.logger.error("MaintenanceReporter: failed to persist history", { error: String(err) });
|
|
11537
11904
|
}
|
|
@@ -11571,20 +11938,20 @@ var TaskRunner = class {
|
|
|
11571
11938
|
* @param origin - Hermes Phase 2 trigger-source tag; defaults to `'cron'`
|
|
11572
11939
|
* when called from the scheduler path.
|
|
11573
11940
|
*/
|
|
11574
|
-
async run(task, origin = "cron") {
|
|
11941
|
+
async run(task, origin = "cron", mode = "fix") {
|
|
11575
11942
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11576
11943
|
let result;
|
|
11577
11944
|
let captured;
|
|
11578
11945
|
try {
|
|
11579
11946
|
switch (task.type) {
|
|
11580
11947
|
case "mechanical-ai": {
|
|
11581
|
-
const out = await this.runMechanicalAI(task, startedAt);
|
|
11948
|
+
const out = await this.runMechanicalAI(task, startedAt, mode);
|
|
11582
11949
|
result = out.result;
|
|
11583
11950
|
captured = out.captured;
|
|
11584
11951
|
break;
|
|
11585
11952
|
}
|
|
11586
11953
|
case "pure-ai":
|
|
11587
|
-
result = await this.runPureAI(task, startedAt);
|
|
11954
|
+
result = await this.runPureAI(task, startedAt, mode);
|
|
11588
11955
|
break;
|
|
11589
11956
|
case "report-only": {
|
|
11590
11957
|
const out = await this.runReportOnly(task, startedAt);
|
|
@@ -11593,7 +11960,7 @@ var TaskRunner = class {
|
|
|
11593
11960
|
break;
|
|
11594
11961
|
}
|
|
11595
11962
|
case "housekeeping": {
|
|
11596
|
-
const out = await this.runHousekeeping(task, startedAt);
|
|
11963
|
+
const out = await this.runHousekeeping(task, startedAt, mode);
|
|
11597
11964
|
result = out.result;
|
|
11598
11965
|
captured = out.captured;
|
|
11599
11966
|
break;
|
|
@@ -11657,7 +12024,8 @@ var TaskRunner = class {
|
|
|
11657
12024
|
findings: r2.findings,
|
|
11658
12025
|
stdout: r2.output,
|
|
11659
12026
|
stderr: r2.stderr,
|
|
11660
|
-
structured: r2.structured ? r2.structured : null
|
|
12027
|
+
structured: r2.structured ? r2.structured : null,
|
|
12028
|
+
executionFailed: false
|
|
11661
12029
|
};
|
|
11662
12030
|
}
|
|
11663
12031
|
if (!task.checkCommand || task.checkCommand.length === 0) {
|
|
@@ -11669,7 +12037,8 @@ var TaskRunner = class {
|
|
|
11669
12037
|
findings: r.findings,
|
|
11670
12038
|
stdout: r.output,
|
|
11671
12039
|
stderr: "",
|
|
11672
|
-
structured: null
|
|
12040
|
+
structured: null,
|
|
12041
|
+
executionFailed: r.executionFailed ?? false
|
|
11673
12042
|
};
|
|
11674
12043
|
}
|
|
11675
12044
|
/**
|
|
@@ -11694,7 +12063,7 @@ var TaskRunner = class {
|
|
|
11694
12063
|
* only if fixable findings exist; persist captured stdout/stderr/context
|
|
11695
12064
|
* via the output store on the way out.
|
|
11696
12065
|
*/
|
|
11697
|
-
async runMechanicalAI(task, startedAt) {
|
|
12066
|
+
async runMechanicalAI(task, startedAt, mode = "fix") {
|
|
11698
12067
|
if (!task.fixSkill) {
|
|
11699
12068
|
return wrap(this.failureResult(task.id, startedAt, "mechanical-ai task missing fixSkill"));
|
|
11700
12069
|
}
|
|
@@ -11716,6 +12085,27 @@ var TaskRunner = class {
|
|
|
11716
12085
|
} catch (err) {
|
|
11717
12086
|
return wrap(this.failureResult(task.id, startedAt, String(err)));
|
|
11718
12087
|
}
|
|
12088
|
+
if (check.executionFailed) {
|
|
12089
|
+
const cls = classifyCheckExecutionFailure(`${check.stdout}
|
|
12090
|
+
${check.stderr}`);
|
|
12091
|
+
if (cls.kind === "unrunnable") {
|
|
12092
|
+
return wrap(
|
|
12093
|
+
this.failureResult(task.id, startedAt, checkExecutionError(check)),
|
|
12094
|
+
captureFromCheck(check)
|
|
12095
|
+
);
|
|
12096
|
+
}
|
|
12097
|
+
if (cls.kind === "precondition") {
|
|
12098
|
+
return wrap(
|
|
12099
|
+
this.skippedResult(task.id, startedAt, cls.reason ?? "precondition not met"),
|
|
12100
|
+
captureFromCheck(check)
|
|
12101
|
+
);
|
|
12102
|
+
}
|
|
12103
|
+
check = {
|
|
12104
|
+
...check,
|
|
12105
|
+
executionFailed: false,
|
|
12106
|
+
findings: check.findings > 0 ? check.findings : recoverFindingsCount(check.stdout)
|
|
12107
|
+
};
|
|
12108
|
+
}
|
|
11719
12109
|
const promptContext = await this.composePromptContext(task);
|
|
11720
12110
|
const baseCaptured = {
|
|
11721
12111
|
stdout: check.stdout,
|
|
@@ -11724,13 +12114,14 @@ var TaskRunner = class {
|
|
|
11724
12114
|
...promptContext ? { context: promptContext } : {}
|
|
11725
12115
|
};
|
|
11726
12116
|
const wakeAgentExplicitlyFalse = check.structured !== null && typeof check.structured === "object" && check.structured.wakeAgent === false;
|
|
11727
|
-
if (check.findings === 0 || wakeAgentExplicitlyFalse) {
|
|
12117
|
+
if (check.findings === 0 || wakeAgentExplicitlyFalse || mode === "report") {
|
|
12118
|
+
const reportedWithFindings = mode === "report" && check.findings > 0;
|
|
11728
12119
|
return {
|
|
11729
12120
|
result: {
|
|
11730
12121
|
taskId: task.id,
|
|
11731
12122
|
startedAt,
|
|
11732
12123
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11733
|
-
status: "no-issues",
|
|
12124
|
+
status: reportedWithFindings ? "success" : "no-issues",
|
|
11734
12125
|
findings: check.findings,
|
|
11735
12126
|
fixed: 0,
|
|
11736
12127
|
prUrl: null,
|
|
@@ -11800,7 +12191,19 @@ var TaskRunner = class {
|
|
|
11800
12191
|
/**
|
|
11801
12192
|
* Pure-AI: always dispatch agent with configured skill.
|
|
11802
12193
|
*/
|
|
11803
|
-
async runPureAI(task, startedAt) {
|
|
12194
|
+
async runPureAI(task, startedAt, mode = "fix") {
|
|
12195
|
+
if (mode === "report") {
|
|
12196
|
+
return {
|
|
12197
|
+
taskId: task.id,
|
|
12198
|
+
startedAt,
|
|
12199
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12200
|
+
status: "no-issues",
|
|
12201
|
+
findings: 0,
|
|
12202
|
+
fixed: 0,
|
|
12203
|
+
prUrl: null,
|
|
12204
|
+
prUpdated: false
|
|
12205
|
+
};
|
|
12206
|
+
}
|
|
11804
12207
|
if (!task.fixSkill) {
|
|
11805
12208
|
return this.failureResult(task.id, startedAt, "pure-ai task missing fixSkill");
|
|
11806
12209
|
}
|
|
@@ -11874,6 +12277,27 @@ var TaskRunner = class {
|
|
|
11874
12277
|
return wrap(this.failureResult(task.id, startedAt, String(err)));
|
|
11875
12278
|
}
|
|
11876
12279
|
const parsed = parseStatusLine(check.stdout);
|
|
12280
|
+
if (parsed === null && check.executionFailed) {
|
|
12281
|
+
const cls = classifyCheckExecutionFailure(`${check.stdout}
|
|
12282
|
+
${check.stderr}`);
|
|
12283
|
+
if (cls.kind === "unrunnable") {
|
|
12284
|
+
return wrap(
|
|
12285
|
+
this.failureResult(task.id, startedAt, checkExecutionError(check)),
|
|
12286
|
+
captureFromCheck(check)
|
|
12287
|
+
);
|
|
12288
|
+
}
|
|
12289
|
+
if (cls.kind === "precondition") {
|
|
12290
|
+
return wrap(
|
|
12291
|
+
this.skippedResult(task.id, startedAt, cls.reason ?? "precondition not met"),
|
|
12292
|
+
captureFromCheck(check)
|
|
12293
|
+
);
|
|
12294
|
+
}
|
|
12295
|
+
check = {
|
|
12296
|
+
...check,
|
|
12297
|
+
executionFailed: false,
|
|
12298
|
+
findings: check.findings > 0 ? check.findings : recoverFindingsCount(check.stdout)
|
|
12299
|
+
};
|
|
12300
|
+
}
|
|
11877
12301
|
const status = parsed?.status ?? "success";
|
|
11878
12302
|
const findings = parsed === null ? check.findings : typeof parsed.candidatesFound === "number" ? parsed.candidatesFound : 0;
|
|
11879
12303
|
const result = {
|
|
@@ -11907,7 +12331,20 @@ var TaskRunner = class {
|
|
|
11907
12331
|
* Hermes Phase 2: a `checkScript` may replace `checkCommand` for housekeeping
|
|
11908
12332
|
* tasks; the runner falls through to the same JSON-status parsing path.
|
|
11909
12333
|
*/
|
|
11910
|
-
async runHousekeeping(task, startedAt) {
|
|
12334
|
+
async runHousekeeping(task, startedAt, mode = "fix") {
|
|
12335
|
+
if (mode === "report") {
|
|
12336
|
+
return wrap({
|
|
12337
|
+
taskId: task.id,
|
|
12338
|
+
startedAt,
|
|
12339
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12340
|
+
status: "skipped",
|
|
12341
|
+
findings: 0,
|
|
12342
|
+
fixed: 0,
|
|
12343
|
+
prUrl: null,
|
|
12344
|
+
prUpdated: false,
|
|
12345
|
+
error: "skipped in report mode: housekeeping may mutate and report runs are read-only"
|
|
12346
|
+
});
|
|
12347
|
+
}
|
|
11911
12348
|
if (!task.checkCommand && !task.checkScript) {
|
|
11912
12349
|
return wrap(
|
|
11913
12350
|
this.failureResult(
|
|
@@ -11974,46 +12411,133 @@ var TaskRunner = class {
|
|
|
11974
12411
|
error
|
|
11975
12412
|
};
|
|
11976
12413
|
}
|
|
12414
|
+
/**
|
|
12415
|
+
* A precondition-gated check (e.g. `predict` with <3 snapshots, or a
|
|
12416
|
+
* graph-backed check before `harness scan`). The command is correctly
|
|
12417
|
+
* configured; the repo just lacks the state it needs. Reported as `skipped`
|
|
12418
|
+
* — distinct from a hard `failure` — carrying the refusal line as the reason
|
|
12419
|
+
* (surfaced in the run-report summary column). ADR 0050.
|
|
12420
|
+
*/
|
|
12421
|
+
skippedResult(taskId, startedAt, reason) {
|
|
12422
|
+
return {
|
|
12423
|
+
taskId,
|
|
12424
|
+
startedAt,
|
|
12425
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12426
|
+
status: "skipped",
|
|
12427
|
+
findings: 0,
|
|
12428
|
+
fixed: 0,
|
|
12429
|
+
prUrl: null,
|
|
12430
|
+
prUpdated: false,
|
|
12431
|
+
error: reason
|
|
12432
|
+
};
|
|
12433
|
+
}
|
|
11977
12434
|
};
|
|
11978
12435
|
function wrap(result, captured) {
|
|
11979
12436
|
return captured ? { result, captured } : { result };
|
|
11980
12437
|
}
|
|
12438
|
+
function checkExecutionError(check) {
|
|
12439
|
+
const detail = (check.stderr || check.stdout || "").trim();
|
|
12440
|
+
return detail ? `check command failed to execute: ${detail}` : "check command failed to execute";
|
|
12441
|
+
}
|
|
12442
|
+
function captureFromCheck(check) {
|
|
12443
|
+
return { stdout: check.stdout, stderr: check.stderr, structured: check.structured };
|
|
12444
|
+
}
|
|
12445
|
+
var PRECONDITION_PATTERNS = [
|
|
12446
|
+
/requires at least \d+ snapshots?/i,
|
|
12447
|
+
/Run ["'`]?harness snapshot/i,
|
|
12448
|
+
/no knowledge graph found/i,
|
|
12449
|
+
/Run ["'`]?harness scan/i,
|
|
12450
|
+
/no graph (?:available|found)/i
|
|
12451
|
+
];
|
|
12452
|
+
var UNRUNNABLE_PATTERNS = [
|
|
12453
|
+
/unknown command/i,
|
|
12454
|
+
/unknown option/i,
|
|
12455
|
+
/\bENOENT\b/,
|
|
12456
|
+
/command not found/i,
|
|
12457
|
+
/cannot find module/i,
|
|
12458
|
+
// A timed-out check did not complete — it is a hard failure, never a
|
|
12459
|
+
// "ran-no-count" success. The runners synthesize this message on SIGTERM.
|
|
12460
|
+
/timed out/i,
|
|
12461
|
+
/\bETIMEDOUT\b/
|
|
12462
|
+
];
|
|
12463
|
+
var TIMEOUT_SIGNATURE = /check timed out after \d+\s*ms/i;
|
|
12464
|
+
function explicitFindingsCount(output) {
|
|
12465
|
+
const after = output.match(/(?:findings?|issues?|violations?|errors?)\s*[:=]\s*(\d+)/i);
|
|
12466
|
+
if (after) return parseInt(after[1], 10);
|
|
12467
|
+
const before = output.match(/(\d+)\s+(?:findings?|issues?|violations?|errors?)\b/i);
|
|
12468
|
+
if (before) return parseInt(before[1], 10);
|
|
12469
|
+
return null;
|
|
12470
|
+
}
|
|
12471
|
+
function recoverFindingsCount(output) {
|
|
12472
|
+
return explicitFindingsCount(output) ?? 1;
|
|
12473
|
+
}
|
|
12474
|
+
function firstMeaningfulLine(output) {
|
|
12475
|
+
const line = output.split("\n").map((l) => l.trim()).find(Boolean);
|
|
12476
|
+
if (!line) return "precondition not met";
|
|
12477
|
+
return line.replace(/^[x✗✓!-]\s*/u, "").trim();
|
|
12478
|
+
}
|
|
12479
|
+
var CLASSIFY_HEAD_LINES = 3;
|
|
12480
|
+
function classifyCheckExecutionFailure(output) {
|
|
12481
|
+
const text = (output ?? "").trim();
|
|
12482
|
+
if (text.length === 0) return { kind: "unrunnable" };
|
|
12483
|
+
if (TIMEOUT_SIGNATURE.test(text)) return { kind: "unrunnable" };
|
|
12484
|
+
if (explicitFindingsCount(text) !== null) return { kind: "ran-no-count" };
|
|
12485
|
+
const head = text.split("\n").filter((l) => l.trim()).slice(0, CLASSIFY_HEAD_LINES).join("\n");
|
|
12486
|
+
for (const re of PRECONDITION_PATTERNS) {
|
|
12487
|
+
if (re.test(head)) return { kind: "precondition", reason: firstMeaningfulLine(text) };
|
|
12488
|
+
}
|
|
12489
|
+
for (const re of UNRUNNABLE_PATTERNS) {
|
|
12490
|
+
if (re.test(head)) return { kind: "unrunnable" };
|
|
12491
|
+
}
|
|
12492
|
+
return { kind: "ran-no-count" };
|
|
12493
|
+
}
|
|
11981
12494
|
function parseStatusLine(output) {
|
|
11982
12495
|
const lines = output.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
11983
12496
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
11984
|
-
const
|
|
11985
|
-
if (
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
const message = typeof obj.message === "string" ? obj.message : "unknown error";
|
|
12010
|
-
return { status: "failure", error: message, rawStatus: "error" };
|
|
12011
|
-
}
|
|
12012
|
-
} catch {
|
|
12013
|
-
}
|
|
12497
|
+
const parsed = parseStatusObjectLine(lines[i]);
|
|
12498
|
+
if (parsed) return parsed;
|
|
12499
|
+
}
|
|
12500
|
+
return null;
|
|
12501
|
+
}
|
|
12502
|
+
function parseStatusObjectLine(line) {
|
|
12503
|
+
if (!line || !line.startsWith("{") || !line.endsWith("}")) return null;
|
|
12504
|
+
let obj;
|
|
12505
|
+
try {
|
|
12506
|
+
obj = JSON.parse(line);
|
|
12507
|
+
} catch {
|
|
12508
|
+
return null;
|
|
12509
|
+
}
|
|
12510
|
+
return classifyStatusObject(obj);
|
|
12511
|
+
}
|
|
12512
|
+
var PHASE_STATUSES = /* @__PURE__ */ new Set(["success", "skipped", "failure", "no-issues"]);
|
|
12513
|
+
var SYNC_SUCCESS_STATUSES = /* @__PURE__ */ new Set(["updated", "no-op"]);
|
|
12514
|
+
function classifyStatusObject(obj) {
|
|
12515
|
+
const s = obj.status;
|
|
12516
|
+
if (typeof s !== "string") return null;
|
|
12517
|
+
if (PHASE_STATUSES.has(s)) return buildPhaseStatus(s, obj);
|
|
12518
|
+
if (SYNC_SUCCESS_STATUSES.has(s)) return { status: "success", rawStatus: s };
|
|
12519
|
+
if (s === "error") {
|
|
12520
|
+
const message = typeof obj.message === "string" ? obj.message : "unknown error";
|
|
12521
|
+
return { status: "failure", error: message, rawStatus: "error" };
|
|
12014
12522
|
}
|
|
12015
12523
|
return null;
|
|
12016
12524
|
}
|
|
12525
|
+
function buildPhaseStatus(status, obj) {
|
|
12526
|
+
const parsed = { status, rawStatus: status };
|
|
12527
|
+
if (typeof obj.candidatesFound === "number") {
|
|
12528
|
+
parsed.candidatesFound = obj.candidatesFound;
|
|
12529
|
+
}
|
|
12530
|
+
if (typeof obj.error === "string") {
|
|
12531
|
+
parsed.error = obj.error;
|
|
12532
|
+
}
|
|
12533
|
+
if (typeof obj.reason === "string") {
|
|
12534
|
+
parsed.reason = obj.reason;
|
|
12535
|
+
}
|
|
12536
|
+
if (typeof obj.detail === "string" && !parsed.error) {
|
|
12537
|
+
parsed.error = `${parsed.reason ?? "skipped"}: ${obj.detail}`;
|
|
12538
|
+
}
|
|
12539
|
+
return parsed;
|
|
12540
|
+
}
|
|
12017
12541
|
|
|
12018
12542
|
// src/maintenance/check-script-runner.ts
|
|
12019
12543
|
var import_node_child_process11 = require("child_process");
|
|
@@ -12126,8 +12650,49 @@ function heuristicResult(stdout, stderr, exitedAbnormally) {
|
|
|
12126
12650
|
};
|
|
12127
12651
|
}
|
|
12128
12652
|
|
|
12653
|
+
// src/maintenance/check-runner.ts
|
|
12654
|
+
var import_node_child_process12 = require("child_process");
|
|
12655
|
+
var import_node_util4 = require("util");
|
|
12656
|
+
var MAINTENANCE_CHECK_MAX_BUFFER = 64 * 1024 * 1024;
|
|
12657
|
+
var MAINTENANCE_CHECK_TIMEOUT_MS = 3e5;
|
|
12658
|
+
var FINDINGS_RE = /(\d+)\s+(?:finding|issue|violation|error)/i;
|
|
12659
|
+
var nodeExecFileAsync = (0, import_node_util4.promisify)(import_node_child_process12.execFile);
|
|
12660
|
+
function isCheckTimeoutError(e) {
|
|
12661
|
+
return e.killed === true || e.signal === "SIGTERM" || e.code === "ETIMEDOUT";
|
|
12662
|
+
}
|
|
12663
|
+
async function runHarnessCheck(spawn7, cwd, opts = {}) {
|
|
12664
|
+
const execFileAsync2 = opts.execFileAsync ?? nodeExecFileAsync;
|
|
12665
|
+
const timeoutMs = opts.timeoutMs ?? MAINTENANCE_CHECK_TIMEOUT_MS;
|
|
12666
|
+
const maxBuffer = opts.maxBuffer ?? MAINTENANCE_CHECK_MAX_BUFFER;
|
|
12667
|
+
try {
|
|
12668
|
+
const { stdout } = await execFileAsync2(spawn7.file, spawn7.args, {
|
|
12669
|
+
cwd,
|
|
12670
|
+
timeout: timeoutMs,
|
|
12671
|
+
maxBuffer
|
|
12672
|
+
});
|
|
12673
|
+
const text = String(stdout);
|
|
12674
|
+
const m = text.match(FINDINGS_RE);
|
|
12675
|
+
const findings = m ? parseInt(m[1], 10) : 0;
|
|
12676
|
+
return { passed: findings === 0, findings, output: text, executionFailed: false };
|
|
12677
|
+
} catch (err) {
|
|
12678
|
+
const e = err;
|
|
12679
|
+
let output = [e.stdout, e.stderr].map((v) => v == null ? "" : String(v)).filter(Boolean).join("\n");
|
|
12680
|
+
if (isCheckTimeoutError(e)) {
|
|
12681
|
+
const note = `check timed out after ${timeoutMs}ms`;
|
|
12682
|
+
output = output.trim() ? `${output}
|
|
12683
|
+
${note}` : note;
|
|
12684
|
+
return { passed: false, findings: 0, output, executionFailed: true };
|
|
12685
|
+
}
|
|
12686
|
+
const m = output.match(FINDINGS_RE);
|
|
12687
|
+
if (m) {
|
|
12688
|
+
return { passed: false, findings: parseInt(m[1], 10), output, executionFailed: false };
|
|
12689
|
+
}
|
|
12690
|
+
return { passed: false, findings: 0, output, executionFailed: true };
|
|
12691
|
+
}
|
|
12692
|
+
}
|
|
12693
|
+
|
|
12129
12694
|
// src/maintenance/output-store.ts
|
|
12130
|
-
var
|
|
12695
|
+
var fs15 = __toESM(require("fs"));
|
|
12131
12696
|
var path20 = __toESM(require("path"));
|
|
12132
12697
|
var DEFAULT_RETENTION = {
|
|
12133
12698
|
runs: 50,
|
|
@@ -12168,13 +12733,13 @@ var TaskOutputStore = class {
|
|
|
12168
12733
|
async write(taskId, entry, retention) {
|
|
12169
12734
|
this.ensureSafeTaskId(taskId);
|
|
12170
12735
|
const dir = this.dirFor(taskId);
|
|
12171
|
-
await
|
|
12736
|
+
await fs15.promises.mkdir(dir, { recursive: true });
|
|
12172
12737
|
const fileName = `${sanitizeIso(entry.completedAt || (/* @__PURE__ */ new Date()).toISOString())}.json`;
|
|
12173
12738
|
const filePath = path20.join(dir, fileName);
|
|
12174
12739
|
const tmpPath = `${filePath}.tmp`;
|
|
12175
12740
|
const payload = JSON.stringify(entry, null, 2);
|
|
12176
|
-
await
|
|
12177
|
-
await
|
|
12741
|
+
await fs15.promises.writeFile(tmpPath, payload, "utf-8");
|
|
12742
|
+
await fs15.promises.rename(tmpPath, filePath);
|
|
12178
12743
|
try {
|
|
12179
12744
|
await this.applyRetention(taskId, retention);
|
|
12180
12745
|
} catch (err) {
|
|
@@ -12225,7 +12790,7 @@ var TaskOutputStore = class {
|
|
|
12225
12790
|
}
|
|
12226
12791
|
async readEntry(filePath) {
|
|
12227
12792
|
try {
|
|
12228
|
-
const buf = await
|
|
12793
|
+
const buf = await fs15.promises.readFile(filePath, "utf-8");
|
|
12229
12794
|
const parsed = JSON.parse(buf);
|
|
12230
12795
|
return parsed;
|
|
12231
12796
|
} catch {
|
|
@@ -12247,7 +12812,7 @@ var TaskOutputStore = class {
|
|
|
12247
12812
|
const toRemove = /* @__PURE__ */ new Set([...overflow, ...aged]);
|
|
12248
12813
|
for (const name of toRemove) {
|
|
12249
12814
|
try {
|
|
12250
|
-
await
|
|
12815
|
+
await fs15.promises.unlink(path20.join(dir, name));
|
|
12251
12816
|
} catch {
|
|
12252
12817
|
}
|
|
12253
12818
|
}
|
|
@@ -12256,7 +12821,7 @@ var TaskOutputStore = class {
|
|
|
12256
12821
|
async function listJsonFilesDescending(dir) {
|
|
12257
12822
|
let names;
|
|
12258
12823
|
try {
|
|
12259
|
-
names = await
|
|
12824
|
+
names = await fs15.promises.readdir(dir);
|
|
12260
12825
|
} catch {
|
|
12261
12826
|
return [];
|
|
12262
12827
|
}
|
|
@@ -12365,7 +12930,7 @@ var fallbackLogger3 = {
|
|
|
12365
12930
|
};
|
|
12366
12931
|
|
|
12367
12932
|
// src/maintenance/custom-task-validator.ts
|
|
12368
|
-
var
|
|
12933
|
+
var import_types31 = require("@harness-engineering/types");
|
|
12369
12934
|
var ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
12370
12935
|
var REQUIRED_FIELDS_BY_TYPE = {
|
|
12371
12936
|
"mechanical-ai": ["branch", "fixSkill"],
|
|
@@ -12375,7 +12940,7 @@ var REQUIRED_FIELDS_BY_TYPE = {
|
|
|
12375
12940
|
};
|
|
12376
12941
|
function validateCustomTasks(customTasks, builtIns, deps = {}) {
|
|
12377
12942
|
const errors = [];
|
|
12378
|
-
if (!customTasks) return (0,
|
|
12943
|
+
if (!customTasks) return (0, import_types31.Ok)(void 0);
|
|
12379
12944
|
const builtInIds = new Set(builtIns.map((t) => t.id));
|
|
12380
12945
|
const customIds = Object.keys(customTasks);
|
|
12381
12946
|
const allIds = /* @__PURE__ */ new Set([...builtInIds, ...customIds]);
|
|
@@ -12385,7 +12950,7 @@ function validateCustomTasks(customTasks, builtIns, deps = {}) {
|
|
|
12385
12950
|
validateOne(id, task, builtInIds, allIds, deps, errors);
|
|
12386
12951
|
}
|
|
12387
12952
|
detectCycles(customTasks, builtIns, errors);
|
|
12388
|
-
return errors.length === 0 ? (0,
|
|
12953
|
+
return errors.length === 0 ? (0, import_types31.Ok)(void 0) : (0, import_types31.Err)(errors);
|
|
12389
12954
|
}
|
|
12390
12955
|
function validateOne(id, task, builtInIds, allIds, deps, errors) {
|
|
12391
12956
|
const prefix = `customTasks.${id}`;
|
|
@@ -12574,6 +13139,11 @@ function deriveSeedPaths(config) {
|
|
|
12574
13139
|
const roadmapPath = config.tracker.kind === "roadmap" && config.tracker.filePath ? config.tracker.filePath : "docs/roadmap.md";
|
|
12575
13140
|
return [".harness/proposals", roadmapPath];
|
|
12576
13141
|
}
|
|
13142
|
+
function normalizeHarnessCommand(command) {
|
|
13143
|
+
if (command.length === 0) return [];
|
|
13144
|
+
if (command[0] === "harness") return command;
|
|
13145
|
+
return ["harness", ...command];
|
|
13146
|
+
}
|
|
12577
13147
|
var Orchestrator = class extends import_node_events.EventEmitter {
|
|
12578
13148
|
state;
|
|
12579
13149
|
config;
|
|
@@ -12700,6 +13270,11 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12700
13270
|
abortControllers = /* @__PURE__ */ new Map();
|
|
12701
13271
|
/** Guards against overlapping ticks when a tick takes longer than the polling interval */
|
|
12702
13272
|
tickInProgress = false;
|
|
13273
|
+
/** Phase 4 (DLane-5): lanes read back from the durable log on first tick, for
|
|
13274
|
+
* observability only — NOT fed into reconciliation. Empty until the first tick. */
|
|
13275
|
+
persistedLanes = { tasks: {} };
|
|
13276
|
+
/** Ensures the lane read-back diagnostic runs at most once (first tick). */
|
|
13277
|
+
laneReadbackDone = false;
|
|
12703
13278
|
/** Timestamp of the last stale branch sweep (at most once per hour) */
|
|
12704
13279
|
lastBranchSweepMs = 0;
|
|
12705
13280
|
/** Current tick-phase activity visible to the dashboard */
|
|
@@ -12773,7 +13348,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12773
13348
|
this.localResolvers.set(name, new LocalModelResolver(resolverOpts));
|
|
12774
13349
|
}
|
|
12775
13350
|
}
|
|
12776
|
-
this.cacheMetrics = new
|
|
13351
|
+
this.cacheMetrics = new import_core17.CacheMetricsRecorder();
|
|
12777
13352
|
if (this.config.agent.backends !== void 0 && Object.keys(this.config.agent.backends).length > 0) {
|
|
12778
13353
|
const sandboxPolicy = this.config.agent.sandboxPolicy === "docker" ? "docker" : "none";
|
|
12779
13354
|
const firstBackendName = Object.keys(this.config.agent.backends)[0];
|
|
@@ -12857,22 +13432,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12857
13432
|
});
|
|
12858
13433
|
webhookDelivery.start();
|
|
12859
13434
|
this.setupNotifications(config.notifications);
|
|
12860
|
-
|
|
12861
|
-
if (otlpCfg) {
|
|
12862
|
-
this.otlpExporter = new import_core16.OTLPExporter({
|
|
12863
|
-
endpoint: otlpCfg.endpoint,
|
|
12864
|
-
...otlpCfg.enabled !== void 0 ? { enabled: otlpCfg.enabled } : {},
|
|
12865
|
-
...otlpCfg.headers !== void 0 ? { headers: otlpCfg.headers } : {},
|
|
12866
|
-
...otlpCfg.flushIntervalMs !== void 0 ? { flushIntervalMs: otlpCfg.flushIntervalMs } : {},
|
|
12867
|
-
...otlpCfg.batchSize !== void 0 ? { batchSize: otlpCfg.batchSize } : {}
|
|
12868
|
-
});
|
|
12869
|
-
this.telemetryFanoutOff = wireTelemetryFanout({
|
|
12870
|
-
bus: this,
|
|
12871
|
-
exporter: this.otlpExporter,
|
|
12872
|
-
webhookDelivery,
|
|
12873
|
-
store: webhookStore
|
|
12874
|
-
});
|
|
12875
|
-
}
|
|
13435
|
+
this.setupTelemetryExport(config, webhookStore, webhookDelivery);
|
|
12876
13436
|
this.server = new OrchestratorServer(this, config.server.port, {
|
|
12877
13437
|
interactionQueue: this.interactionQueue,
|
|
12878
13438
|
webhooks: {
|
|
@@ -12895,24 +13455,8 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12895
13455
|
analysisArchive: this.analysisArchive,
|
|
12896
13456
|
roadmapPath: config.tracker.filePath ?? null,
|
|
12897
13457
|
dispatchAdHoc: this.dispatchAdHoc.bind(this),
|
|
12898
|
-
getLocalModelStatus: () =>
|
|
12899
|
-
|
|
12900
|
-
return first.done ? null : first.value.getStatus();
|
|
12901
|
-
},
|
|
12902
|
-
getLocalModelStatuses: () => {
|
|
12903
|
-
const backends = this.config.agent.backends ?? {};
|
|
12904
|
-
const out = [];
|
|
12905
|
-
for (const [name, resolver] of this.localResolvers) {
|
|
12906
|
-
const def = backends[name];
|
|
12907
|
-
if (!def || def.type !== "local" && def.type !== "pi") continue;
|
|
12908
|
-
out.push({
|
|
12909
|
-
...resolver.getStatus(),
|
|
12910
|
-
backendName: name,
|
|
12911
|
-
endpoint: def.endpoint
|
|
12912
|
-
});
|
|
12913
|
-
}
|
|
12914
|
-
return out;
|
|
12915
|
-
}
|
|
13458
|
+
getLocalModelStatus: () => this.getFirstLocalModelStatus(),
|
|
13459
|
+
getLocalModelStatuses: () => this.buildLocalModelStatuses()
|
|
12916
13460
|
});
|
|
12917
13461
|
this.server.setRecorder(this.recorder);
|
|
12918
13462
|
this.interactionQueue.onPush((interaction) => {
|
|
@@ -12920,6 +13464,56 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12920
13464
|
});
|
|
12921
13465
|
}
|
|
12922
13466
|
}
|
|
13467
|
+
/**
|
|
13468
|
+
* Phase 5: construct the OTLP/HTTP trace exporter and wire telemetry fanout.
|
|
13469
|
+
* Only fires when the operator configures `telemetry.export.otlp` in
|
|
13470
|
+
* harness.config.json. Extracted from the server-init block in the
|
|
13471
|
+
* constructor to keep that block's cyclomatic complexity under threshold.
|
|
13472
|
+
*/
|
|
13473
|
+
setupTelemetryExport(config, webhookStore, webhookDelivery) {
|
|
13474
|
+
const otlpCfg = config.telemetry?.export?.otlp;
|
|
13475
|
+
if (!otlpCfg) return;
|
|
13476
|
+
this.otlpExporter = new import_core17.OTLPExporter({
|
|
13477
|
+
endpoint: otlpCfg.endpoint,
|
|
13478
|
+
...otlpCfg.enabled !== void 0 ? { enabled: otlpCfg.enabled } : {},
|
|
13479
|
+
...otlpCfg.headers !== void 0 ? { headers: otlpCfg.headers } : {},
|
|
13480
|
+
...otlpCfg.flushIntervalMs !== void 0 ? { flushIntervalMs: otlpCfg.flushIntervalMs } : {},
|
|
13481
|
+
...otlpCfg.batchSize !== void 0 ? { batchSize: otlpCfg.batchSize } : {}
|
|
13482
|
+
});
|
|
13483
|
+
this.telemetryFanoutOff = wireTelemetryFanout({
|
|
13484
|
+
bus: this,
|
|
13485
|
+
exporter: this.otlpExporter,
|
|
13486
|
+
webhookDelivery,
|
|
13487
|
+
store: webhookStore
|
|
13488
|
+
});
|
|
13489
|
+
}
|
|
13490
|
+
/**
|
|
13491
|
+
* Deprecated alias for /api/v1/local-model/status (Spec 1 endpoint retained
|
|
13492
|
+
* as a compat shim per spec line 35; superseded by getLocalModelStatuses for
|
|
13493
|
+
* the multi-local UI). Returns the first-registered resolver's status.
|
|
13494
|
+
*/
|
|
13495
|
+
getFirstLocalModelStatus() {
|
|
13496
|
+
const first = this.localResolvers.values().next();
|
|
13497
|
+
return first.done ? null : first.value.getStatus();
|
|
13498
|
+
}
|
|
13499
|
+
/**
|
|
13500
|
+
* SC38: build NamedLocalModelStatus[] from each registered resolver, tagged
|
|
13501
|
+
* with its backendName + endpoint from the config.
|
|
13502
|
+
*/
|
|
13503
|
+
buildLocalModelStatuses() {
|
|
13504
|
+
const backends = this.config.agent.backends ?? {};
|
|
13505
|
+
const out = [];
|
|
13506
|
+
for (const [name, resolver] of this.localResolvers) {
|
|
13507
|
+
const def = backends[name];
|
|
13508
|
+
if (!def || def.type !== "local" && def.type !== "pi") continue;
|
|
13509
|
+
out.push({
|
|
13510
|
+
...resolver.getStatus(),
|
|
13511
|
+
backendName: name,
|
|
13512
|
+
endpoint: def.endpoint
|
|
13513
|
+
});
|
|
13514
|
+
}
|
|
13515
|
+
return out;
|
|
13516
|
+
}
|
|
12923
13517
|
createTracker() {
|
|
12924
13518
|
if (this.config.tracker.kind === "github-issues") {
|
|
12925
13519
|
const trackerCfg = {
|
|
@@ -12928,7 +13522,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12928
13522
|
...this.config.tracker.apiKey ? { token: this.config.tracker.apiKey } : {},
|
|
12929
13523
|
...this.config.tracker.endpoint ? { apiBase: this.config.tracker.endpoint } : {}
|
|
12930
13524
|
};
|
|
12931
|
-
const clientResult = (0,
|
|
13525
|
+
const clientResult = (0, import_core16.createTrackerClient)(trackerCfg);
|
|
12932
13526
|
if (!clientResult.ok) throw clientResult.error;
|
|
12933
13527
|
return new GitHubIssuesIssueTrackerAdapter(clientResult.value, this.config.tracker);
|
|
12934
13528
|
}
|
|
@@ -12946,48 +13540,30 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
12946
13540
|
const logger = this.logger;
|
|
12947
13541
|
const checkRunner = {
|
|
12948
13542
|
run: async (command, cwd) => {
|
|
12949
|
-
const
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
const [cmd, ...args] = command;
|
|
12953
|
-
if (!cmd) return { passed: true, findings: 0, output: "" };
|
|
12954
|
-
try {
|
|
12955
|
-
const { stdout } = await execFileAsync2(cmd, args, { cwd, timeout: 12e4 });
|
|
12956
|
-
const findingsMatch = stdout.match(/(\d+)\s+(?:finding|issue|violation|error)/i);
|
|
12957
|
-
const findings = findingsMatch ? parseInt(findingsMatch[1], 10) : 0;
|
|
12958
|
-
return { passed: findings === 0, findings, output: stdout };
|
|
12959
|
-
} catch (err) {
|
|
12960
|
-
const error = err;
|
|
12961
|
-
const output = [error.stdout, error.stderr].filter(Boolean).join("\n");
|
|
12962
|
-
const findingsMatch = output.match(/(\d+)\s+(?:finding|issue|violation|error)/i);
|
|
12963
|
-
const findings = findingsMatch ? parseInt(findingsMatch[1], 10) : 1;
|
|
12964
|
-
return { passed: false, findings, output };
|
|
12965
|
-
}
|
|
12966
|
-
}
|
|
12967
|
-
};
|
|
12968
|
-
const agentDispatcher = {
|
|
12969
|
-
dispatch: async (skill, branch, backendName, cwd) => {
|
|
12970
|
-
logger.info(
|
|
12971
|
-
"Maintenance agent dispatcher invoked (stub \u2014 skill dispatch integration pending)",
|
|
12972
|
-
{
|
|
12973
|
-
skill,
|
|
12974
|
-
branch,
|
|
12975
|
-
backendName,
|
|
12976
|
-
cwd
|
|
12977
|
-
}
|
|
12978
|
-
);
|
|
12979
|
-
return { producedCommits: false, fixed: 0 };
|
|
13543
|
+
const [cmd, ...args] = normalizeHarnessCommand(command);
|
|
13544
|
+
if (!cmd) return { passed: true, findings: 0, output: "", executionFailed: false };
|
|
13545
|
+
return runHarnessCheck({ file: cmd, args }, cwd);
|
|
12980
13546
|
}
|
|
12981
13547
|
};
|
|
13548
|
+
const resolveBackend2 = makeBackendResolver(this.getBackends());
|
|
13549
|
+
const agentDispatcher = createAgentDispatcher({
|
|
13550
|
+
resolveBackend: resolveBackend2,
|
|
13551
|
+
git: (args, cwd) => (0, import_node_child_process13.execFileSync)("git", args, { cwd, encoding: "utf-8" }).toString().trim(),
|
|
13552
|
+
logger
|
|
13553
|
+
});
|
|
12982
13554
|
const commandExecutor = {
|
|
12983
13555
|
exec: async (command, cwd) => {
|
|
12984
13556
|
const { execFile: execFile7 } = await import("child_process");
|
|
12985
|
-
const { promisify:
|
|
12986
|
-
const execFileAsync2 =
|
|
12987
|
-
const [cmd, ...args] = command;
|
|
13557
|
+
const { promisify: promisify6 } = await import("util");
|
|
13558
|
+
const execFileAsync2 = promisify6(execFile7);
|
|
13559
|
+
const [cmd, ...args] = normalizeHarnessCommand(command);
|
|
12988
13560
|
if (!cmd) return { stdout: "" };
|
|
12989
13561
|
try {
|
|
12990
|
-
const { stdout } = await execFileAsync2(cmd, args, {
|
|
13562
|
+
const { stdout } = await execFileAsync2(cmd, args, {
|
|
13563
|
+
cwd,
|
|
13564
|
+
timeout: MAINTENANCE_CHECK_TIMEOUT_MS,
|
|
13565
|
+
maxBuffer: MAINTENANCE_CHECK_MAX_BUFFER
|
|
13566
|
+
});
|
|
12991
13567
|
return { stdout: String(stdout) };
|
|
12992
13568
|
} catch (err) {
|
|
12993
13569
|
logger.warn("Maintenance command execution failed", {
|
|
@@ -13122,6 +13698,10 @@ ${messages}`);
|
|
|
13122
13698
|
async asyncTick() {
|
|
13123
13699
|
await this.ensureClaimManager();
|
|
13124
13700
|
await this.intelligenceRunner.loadPersistedData();
|
|
13701
|
+
if (!this.laneReadbackDone) {
|
|
13702
|
+
this.laneReadbackDone = true;
|
|
13703
|
+
await this.readBackPersistedLanes();
|
|
13704
|
+
}
|
|
13125
13705
|
const nowMs = Date.now();
|
|
13126
13706
|
this.setTickActivity("fetching", "Polling tracker for candidates");
|
|
13127
13707
|
const candidatesResult = await this.tracker.fetchCandidateIssues();
|
|
@@ -13274,12 +13854,48 @@ ${messages}`);
|
|
|
13274
13854
|
break;
|
|
13275
13855
|
case "escalate":
|
|
13276
13856
|
await this.handleEscalation(effect);
|
|
13857
|
+
await this.persistLaneSafe(effect.issueId, "abandon");
|
|
13277
13858
|
break;
|
|
13278
13859
|
case "claim":
|
|
13279
13860
|
await this.handleClaimEffect(effect);
|
|
13280
13861
|
break;
|
|
13281
13862
|
}
|
|
13282
13863
|
}
|
|
13864
|
+
/**
|
|
13865
|
+
* Phase 4 (DLane-5): persist an orchestrator lane transition to the durable
|
|
13866
|
+
* core event log at the effect boundary. NEVER throws — `persistLane` returns
|
|
13867
|
+
* an `Err` Result on failure, which is logged and swallowed here so a
|
|
13868
|
+
* lane-persistence failure can never break orchestrator dispatch.
|
|
13869
|
+
*/
|
|
13870
|
+
async persistLaneSafe(issueId, signal) {
|
|
13871
|
+
const r = await persistLane(this.projectRoot, issueId, signal);
|
|
13872
|
+
if (!r.ok) {
|
|
13873
|
+
this.logger.warn(`lane persist failed for ${issueId} (${signal}): ${r.error.message}`);
|
|
13874
|
+
}
|
|
13875
|
+
}
|
|
13876
|
+
/**
|
|
13877
|
+
* Phase 4 (DLane-5): read persisted task lanes back from the durable log and
|
|
13878
|
+
* log a one-line summary. Stores the projection on `this.persistedLanes` for
|
|
13879
|
+
* observability. Read-only — never feeds reconciliation, never throws.
|
|
13880
|
+
*/
|
|
13881
|
+
async readBackPersistedLanes() {
|
|
13882
|
+
const lanes = await readPersistedLanes(this.projectRoot);
|
|
13883
|
+
this.persistedLanes = lanes;
|
|
13884
|
+
const entries = Object.keys(lanes.tasks).map((id) => `${id}:${lanes.tasks[id]?.lane}`);
|
|
13885
|
+
const nonTerminal = entries.filter((e) => !e.endsWith(":done") && !e.endsWith(":canceled"));
|
|
13886
|
+
this.logger.info(
|
|
13887
|
+
`Lane read-back on startup: ${entries.length} persisted task(s), ${nonTerminal.length} non-terminal`,
|
|
13888
|
+
{ nonTerminal }
|
|
13889
|
+
);
|
|
13890
|
+
}
|
|
13891
|
+
/**
|
|
13892
|
+
* Phase 4 (DLane-5): the task lanes most recently read back from the durable
|
|
13893
|
+
* log on startup, exposed for external observability. Read-only — a fresh
|
|
13894
|
+
* `{ tasks: {} }` until the first tick's read-back has run.
|
|
13895
|
+
*/
|
|
13896
|
+
getPersistedLanes() {
|
|
13897
|
+
return this.persistedLanes;
|
|
13898
|
+
}
|
|
13283
13899
|
/**
|
|
13284
13900
|
* Guards workspace cleanup by checking whether the agent pushed a branch
|
|
13285
13901
|
* that does not yet have a pull request. If so, the worktree is preserved
|
|
@@ -13312,7 +13928,7 @@ ${messages}`);
|
|
|
13312
13928
|
{ issueId }
|
|
13313
13929
|
);
|
|
13314
13930
|
await this.interactionQueue.push({
|
|
13315
|
-
id: `interaction-${(0,
|
|
13931
|
+
id: `interaction-${(0, import_node_crypto15.randomUUID)()}`,
|
|
13316
13932
|
issueId,
|
|
13317
13933
|
type: "needs-human",
|
|
13318
13934
|
reasons: [`Agent pushed branch "${branch}" but did not create a PR. Worktree preserved.`],
|
|
@@ -13388,7 +14004,7 @@ ${messages}`);
|
|
|
13388
14004
|
{ issueId: effect.issueId }
|
|
13389
14005
|
);
|
|
13390
14006
|
await this.interactionQueue.push({
|
|
13391
|
-
id: `interaction-${(0,
|
|
14007
|
+
id: `interaction-${(0, import_node_crypto15.randomUUID)()}`,
|
|
13392
14008
|
issueId: effect.issueId,
|
|
13393
14009
|
type: "needs-human",
|
|
13394
14010
|
reasons: effect.reasons,
|
|
@@ -13466,6 +14082,7 @@ ${messages}`);
|
|
|
13466
14082
|
}
|
|
13467
14083
|
return;
|
|
13468
14084
|
}
|
|
14085
|
+
await this.persistLaneSafe(effect.issue.id, "claim");
|
|
13469
14086
|
await this.postClaimComment(effect.issue);
|
|
13470
14087
|
await this.dispatchIssue(effect.issue, effect.attempt, effect.backend);
|
|
13471
14088
|
}
|
|
@@ -13484,12 +14101,12 @@ ${messages}`);
|
|
|
13484
14101
|
async postLifecycleComment(identifier, externalId, event) {
|
|
13485
14102
|
try {
|
|
13486
14103
|
if (!externalId) return;
|
|
13487
|
-
const trackerConfig = (0,
|
|
14104
|
+
const trackerConfig = (0, import_core16.loadTrackerSyncConfig)(this.projectRoot);
|
|
13488
14105
|
if (!trackerConfig) return;
|
|
13489
14106
|
const token = process.env.GITHUB_TOKEN;
|
|
13490
14107
|
if (!token) return;
|
|
13491
14108
|
const orchestratorId = await this.orchestratorIdPromise;
|
|
13492
|
-
const adapter = new
|
|
14109
|
+
const adapter = new import_core16.GitHubIssuesSyncAdapter({ token, config: trackerConfig });
|
|
13493
14110
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
13494
14111
|
const actionMap = {
|
|
13495
14112
|
claimed: "Dispatching agent for autonomous execution",
|
|
@@ -13526,6 +14143,7 @@ ${messages}`);
|
|
|
13526
14143
|
this.logger.info(`Dispatching issue: ${issue.identifier} (attempt ${attempt})`, {
|
|
13527
14144
|
issueId: issue.id
|
|
13528
14145
|
});
|
|
14146
|
+
await this.persistLaneSafe(issue.id, "dispatch");
|
|
13529
14147
|
try {
|
|
13530
14148
|
const workspaceResult = await this.workspace.ensureWorkspace(issue.identifier);
|
|
13531
14149
|
if (!workspaceResult.ok) throw workspaceResult.error;
|
|
@@ -13562,7 +14180,7 @@ ${messages}`);
|
|
|
13562
14180
|
...f.line !== void 0 ? { line: f.line } : {}
|
|
13563
14181
|
}))
|
|
13564
14182
|
);
|
|
13565
|
-
(0,
|
|
14183
|
+
(0, import_core15.writeTaint)(
|
|
13566
14184
|
workspacePath,
|
|
13567
14185
|
issue.id,
|
|
13568
14186
|
"Medium-severity injection patterns found in workspace config files",
|
|
@@ -13734,6 +14352,7 @@ ${messages}`);
|
|
|
13734
14352
|
* Informs the state machine that an agent worker has exited.
|
|
13735
14353
|
*/
|
|
13736
14354
|
async emitWorkerExit(issueId, reason, attempt, error) {
|
|
14355
|
+
await this.persistLaneSafe(issueId, reason === "normal" ? "success" : "failure");
|
|
13737
14356
|
await this.completionHandler.handleWorkerExit(
|
|
13738
14357
|
issueId,
|
|
13739
14358
|
reason,
|
|
@@ -14269,11 +14888,11 @@ function launchTUI(orchestrator) {
|
|
|
14269
14888
|
}
|
|
14270
14889
|
|
|
14271
14890
|
// src/maintenance/sync-main.ts
|
|
14272
|
-
var
|
|
14273
|
-
var
|
|
14891
|
+
var import_node_child_process14 = require("child_process");
|
|
14892
|
+
var import_node_util5 = require("util");
|
|
14274
14893
|
var DEFAULT_TIMEOUT_MS3 = 6e4;
|
|
14275
14894
|
async function git(execFileFn, args, cwd, timeoutMs) {
|
|
14276
|
-
const exec = (0,
|
|
14895
|
+
const exec = (0, import_node_util5.promisify)(execFileFn);
|
|
14277
14896
|
const { stdout, stderr } = await exec("git", args, { cwd, timeout: timeoutMs });
|
|
14278
14897
|
return { stdout: String(stdout), stderr: String(stderr) };
|
|
14279
14898
|
}
|
|
@@ -14335,7 +14954,7 @@ async function isAncestor(execFileFn, a, b, cwd, timeoutMs) {
|
|
|
14335
14954
|
}
|
|
14336
14955
|
}
|
|
14337
14956
|
async function syncMain(repoRoot, opts = {}) {
|
|
14338
|
-
const execFileFn = opts.execFileFn ??
|
|
14957
|
+
const execFileFn = opts.execFileFn ?? import_node_child_process14.execFile;
|
|
14339
14958
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
|
|
14340
14959
|
try {
|
|
14341
14960
|
const originRef = await resolveOriginDefault(execFileFn, repoRoot, timeoutMs);
|
|
@@ -14412,11 +15031,59 @@ async function syncMain(repoRoot, opts = {}) {
|
|
|
14412
15031
|
}
|
|
14413
15032
|
}
|
|
14414
15033
|
|
|
15034
|
+
// src/maintenance/overdue.ts
|
|
15035
|
+
var LOOKBACK_DAYS = 366;
|
|
15036
|
+
function previousFireTime(schedule, now) {
|
|
15037
|
+
let day = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
15038
|
+
for (let d = 0; d <= LOOKBACK_DAYS; d++) {
|
|
15039
|
+
if (cronMatchesDate(schedule, day)) {
|
|
15040
|
+
const dayStartMs = day.getTime();
|
|
15041
|
+
const scanStart = d === 0 ? new Date(
|
|
15042
|
+
now.getFullYear(),
|
|
15043
|
+
now.getMonth(),
|
|
15044
|
+
now.getDate(),
|
|
15045
|
+
now.getHours(),
|
|
15046
|
+
now.getMinutes()
|
|
15047
|
+
) : new Date(day.getFullYear(), day.getMonth(), day.getDate(), 23, 59);
|
|
15048
|
+
for (let ms = scanStart.getTime(); ms >= dayStartMs; ms -= 6e4) {
|
|
15049
|
+
const candidate = new Date(ms);
|
|
15050
|
+
if (cronMatchesNow(schedule, candidate)) return candidate;
|
|
15051
|
+
}
|
|
15052
|
+
}
|
|
15053
|
+
day = new Date(day.getFullYear(), day.getMonth(), day.getDate() - 1);
|
|
15054
|
+
}
|
|
15055
|
+
return null;
|
|
15056
|
+
}
|
|
15057
|
+
function isSatisfyingRun(r) {
|
|
15058
|
+
return r.status === "success" || r.status === "no-issues";
|
|
15059
|
+
}
|
|
15060
|
+
function isOverdue(task, history, now) {
|
|
15061
|
+
const satisfyingRuns = history.filter((r) => r.taskId === task.id && isSatisfyingRun(r));
|
|
15062
|
+
const fire = previousFireTime(task.schedule, now);
|
|
15063
|
+
if (fire === null) return satisfyingRuns.length === 0;
|
|
15064
|
+
const fireMs = fire.getTime();
|
|
15065
|
+
const satisfied = satisfyingRuns.some((r) => new Date(r.completedAt).getTime() >= fireMs);
|
|
15066
|
+
return !satisfied;
|
|
15067
|
+
}
|
|
15068
|
+
function selectTasks(tasks, history, filter) {
|
|
15069
|
+
const eligible = tasks.filter((t) => t.excludeFromHumanSweep !== true);
|
|
15070
|
+
switch (filter.mode) {
|
|
15071
|
+
case "all":
|
|
15072
|
+
return eligible;
|
|
15073
|
+
case "ids": {
|
|
15074
|
+
const wanted = new Set(filter.ids ?? []);
|
|
15075
|
+
return eligible.filter((t) => wanted.has(t.id));
|
|
15076
|
+
}
|
|
15077
|
+
case "overdue":
|
|
15078
|
+
return eligible.filter((t) => isOverdue(t, history, filter.now));
|
|
15079
|
+
}
|
|
15080
|
+
}
|
|
15081
|
+
|
|
14415
15082
|
// src/sessions/search-index.ts
|
|
14416
|
-
var
|
|
15083
|
+
var fs16 = __toESM(require("fs"));
|
|
14417
15084
|
var path22 = __toESM(require("path"));
|
|
14418
15085
|
var import_better_sqlite32 = __toESM(require("better-sqlite3"));
|
|
14419
|
-
var
|
|
15086
|
+
var import_types32 = require("@harness-engineering/types");
|
|
14420
15087
|
var SEARCH_INDEX_FILE = "search-index.sqlite";
|
|
14421
15088
|
var SCHEMA_SQL2 = `
|
|
14422
15089
|
CREATE TABLE IF NOT EXISTS session_docs (
|
|
@@ -14474,7 +15141,7 @@ var SqliteSearchIndex = class {
|
|
|
14474
15141
|
removeSessionStmt;
|
|
14475
15142
|
totalStmt;
|
|
14476
15143
|
constructor(dbPath) {
|
|
14477
|
-
|
|
15144
|
+
fs16.mkdirSync(path22.dirname(dbPath), { recursive: true });
|
|
14478
15145
|
this.db = new import_better_sqlite32.default(dbPath);
|
|
14479
15146
|
this.db.pragma("journal_mode = WAL");
|
|
14480
15147
|
this.db.pragma("synchronous = NORMAL");
|
|
@@ -14574,18 +15241,18 @@ function openSearchIndex(projectPath) {
|
|
|
14574
15241
|
return new SqliteSearchIndex(searchIndexPath(projectPath));
|
|
14575
15242
|
}
|
|
14576
15243
|
function indexSessionDirectory(idx, args) {
|
|
14577
|
-
const kinds = args.fileKinds ?? [...
|
|
15244
|
+
const kinds = args.fileKinds ?? [...import_types32.INDEXED_FILE_KINDS];
|
|
14578
15245
|
const cap = args.maxBytesPerBody ?? 256 * 1024;
|
|
14579
15246
|
let docsWritten = 0;
|
|
14580
15247
|
for (const kind of kinds) {
|
|
14581
15248
|
const fileName = FILE_KIND_TO_FILENAME[kind];
|
|
14582
15249
|
const filePath = path22.join(args.sessionDir, fileName);
|
|
14583
|
-
if (!
|
|
14584
|
-
let body =
|
|
15250
|
+
if (!fs16.existsSync(filePath)) continue;
|
|
15251
|
+
let body = fs16.readFileSync(filePath, "utf8");
|
|
14585
15252
|
if (Buffer.byteLength(body, "utf8") > cap) {
|
|
14586
15253
|
body = body.slice(0, cap) + "\n\n[TRUNCATED]";
|
|
14587
15254
|
}
|
|
14588
|
-
const stat =
|
|
15255
|
+
const stat = fs16.statSync(filePath);
|
|
14589
15256
|
const relPath = path22.relative(args.projectPath, filePath).replaceAll("\\", "/");
|
|
14590
15257
|
idx.upsertSessionDoc({
|
|
14591
15258
|
sessionId: args.sessionId,
|
|
@@ -14607,8 +15274,8 @@ function reindexFromArchive(projectPath, opts = {}) {
|
|
|
14607
15274
|
idx.resetArchived();
|
|
14608
15275
|
let sessionsIndexed = 0;
|
|
14609
15276
|
let docsWritten = 0;
|
|
14610
|
-
if (
|
|
14611
|
-
const entries =
|
|
15277
|
+
if (fs16.existsSync(archiveBase)) {
|
|
15278
|
+
const entries = fs16.readdirSync(archiveBase, { withFileTypes: true });
|
|
14612
15279
|
for (const entry of entries) {
|
|
14613
15280
|
if (!entry.isDirectory()) continue;
|
|
14614
15281
|
const sessionDir = path22.join(archiveBase, entry.name);
|
|
@@ -14631,10 +15298,10 @@ function reindexFromArchive(projectPath, opts = {}) {
|
|
|
14631
15298
|
}
|
|
14632
15299
|
|
|
14633
15300
|
// src/sessions/summarize.ts
|
|
14634
|
-
var
|
|
15301
|
+
var fs17 = __toESM(require("fs"));
|
|
14635
15302
|
var path23 = __toESM(require("path"));
|
|
14636
|
-
var import_types32 = require("@harness-engineering/types");
|
|
14637
15303
|
var import_types33 = require("@harness-engineering/types");
|
|
15304
|
+
var import_types34 = require("@harness-engineering/types");
|
|
14638
15305
|
var LLM_SUMMARY_FILE = "llm-summary.md";
|
|
14639
15306
|
var SUMMARY_INPUT_FILES = [
|
|
14640
15307
|
{ filename: "summary.md", kind: "summary" },
|
|
@@ -14661,9 +15328,9 @@ function readInputCorpus(archiveDir) {
|
|
|
14661
15328
|
const parts = [];
|
|
14662
15329
|
for (const { filename, kind } of SUMMARY_INPUT_FILES) {
|
|
14663
15330
|
const p = path23.join(archiveDir, filename);
|
|
14664
|
-
if (!
|
|
15331
|
+
if (!fs17.existsSync(p)) continue;
|
|
14665
15332
|
try {
|
|
14666
|
-
const content =
|
|
15333
|
+
const content = fs17.readFileSync(p, "utf8");
|
|
14667
15334
|
if (content.trim().length === 0) continue;
|
|
14668
15335
|
parts.push(`## FILE: ${kind}
|
|
14669
15336
|
|
|
@@ -14725,17 +15392,17 @@ status: failed
|
|
|
14725
15392
|
|
|
14726
15393
|
- reason: ${reason}
|
|
14727
15394
|
`;
|
|
14728
|
-
|
|
15395
|
+
fs17.writeFileSync(filePath, body, "utf8");
|
|
14729
15396
|
return filePath;
|
|
14730
15397
|
}
|
|
14731
15398
|
async function summarizeArchivedSession(ctx) {
|
|
14732
15399
|
const writeStubOnError = ctx.writeStubOnError ?? true;
|
|
14733
|
-
if (!
|
|
14734
|
-
return (0,
|
|
15400
|
+
if (!fs17.existsSync(ctx.archiveDir)) {
|
|
15401
|
+
return (0, import_types34.Err)(new Error(`archive directory not found: ${ctx.archiveDir}`));
|
|
14735
15402
|
}
|
|
14736
15403
|
const corpus = readInputCorpus(ctx.archiveDir);
|
|
14737
15404
|
if (corpus.trim().length === 0) {
|
|
14738
|
-
return (0,
|
|
15405
|
+
return (0, import_types34.Err)(new Error(`no summary input files found in ${ctx.archiveDir}`));
|
|
14739
15406
|
}
|
|
14740
15407
|
const inputBudgetTokens = ctx.config?.inputBudgetTokens ?? DEFAULT_INPUT_BUDGET_TOKENS;
|
|
14741
15408
|
const truncated = truncateForBudget(corpus, inputBudgetTokens);
|
|
@@ -14744,7 +15411,7 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14744
15411
|
const analyzeOpts = {
|
|
14745
15412
|
prompt,
|
|
14746
15413
|
systemPrompt: SYSTEM_PROMPT,
|
|
14747
|
-
responseSchema:
|
|
15414
|
+
responseSchema: import_types33.SessionSummarySchema,
|
|
14748
15415
|
...ctx.config?.model && { model: ctx.config.model }
|
|
14749
15416
|
};
|
|
14750
15417
|
let response;
|
|
@@ -14768,11 +15435,11 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14768
15435
|
} catch {
|
|
14769
15436
|
}
|
|
14770
15437
|
}
|
|
14771
|
-
return (0,
|
|
15438
|
+
return (0, import_types34.Err)(
|
|
14772
15439
|
new Error(`session summary failed: ${reason}` + (stubPath ? ` (stub: ${stubPath})` : ""))
|
|
14773
15440
|
);
|
|
14774
15441
|
}
|
|
14775
|
-
const parsed =
|
|
15442
|
+
const parsed = import_types33.SessionSummarySchema.safeParse(response.result);
|
|
14776
15443
|
if (!parsed.success) {
|
|
14777
15444
|
const reason = `schema validation failed: ${parsed.error.message}`;
|
|
14778
15445
|
ctx.logger?.warn?.("session summary: invalid provider payload", { reason });
|
|
@@ -14782,7 +15449,7 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14782
15449
|
} catch {
|
|
14783
15450
|
}
|
|
14784
15451
|
}
|
|
14785
|
-
return (0,
|
|
15452
|
+
return (0, import_types34.Err)(new Error(reason));
|
|
14786
15453
|
}
|
|
14787
15454
|
const meta = {
|
|
14788
15455
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -14793,8 +15460,8 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14793
15460
|
};
|
|
14794
15461
|
const filePath = path23.join(ctx.archiveDir, LLM_SUMMARY_FILE);
|
|
14795
15462
|
const body = renderLlmSummaryMarkdown(parsed.data, meta);
|
|
14796
|
-
|
|
14797
|
-
return (0,
|
|
15463
|
+
fs17.writeFileSync(filePath, body, "utf8");
|
|
15464
|
+
return (0, import_types34.Ok)({ summary: parsed.data, meta, filePath });
|
|
14798
15465
|
}
|
|
14799
15466
|
function isSummaryEnabled(config) {
|
|
14800
15467
|
if (!config) return false;
|
|
@@ -14874,13 +15541,18 @@ function buildArchiveHooks(opts) {
|
|
|
14874
15541
|
BUILT_IN_TASKS,
|
|
14875
15542
|
BackendDefSchema,
|
|
14876
15543
|
BackendRouter,
|
|
15544
|
+
CheckScriptRunner,
|
|
14877
15545
|
ClaimManager,
|
|
14878
15546
|
GateNotReadyError,
|
|
14879
15547
|
GateRunError,
|
|
14880
15548
|
InteractionQueue,
|
|
15549
|
+
LinearGraphQLClient,
|
|
14881
15550
|
LinearGraphQLStub,
|
|
14882
15551
|
LocalModelResolver,
|
|
15552
|
+
MAINTENANCE_CHECK_MAX_BUFFER,
|
|
15553
|
+
MAINTENANCE_CHECK_TIMEOUT_MS,
|
|
14883
15554
|
MAX_ATTEMPTS,
|
|
15555
|
+
MaintenanceReporter,
|
|
14884
15556
|
MockBackend,
|
|
14885
15557
|
ORCHESTRATOR_IDENTITY_FILE,
|
|
14886
15558
|
Orchestrator,
|
|
@@ -14898,6 +15570,7 @@ function buildArchiveHooks(opts) {
|
|
|
14898
15570
|
SqliteSearchIndex,
|
|
14899
15571
|
StreamRecorder,
|
|
14900
15572
|
TaskOutputStore,
|
|
15573
|
+
TaskRunner,
|
|
14901
15574
|
TokenStore,
|
|
14902
15575
|
WebhookQueue,
|
|
14903
15576
|
WorkflowLoader,
|
|
@@ -14908,7 +15581,9 @@ function buildArchiveHooks(opts) {
|
|
|
14908
15581
|
buildArchiveHooks,
|
|
14909
15582
|
calculateRetryDelay,
|
|
14910
15583
|
canDispatch,
|
|
15584
|
+
classifyCheckExecutionFailure,
|
|
14911
15585
|
computeRateLimitDelay,
|
|
15586
|
+
createAgentDispatcher,
|
|
14912
15587
|
createBackend,
|
|
14913
15588
|
createEmptyState,
|
|
14914
15589
|
crossFieldRoutingIssues,
|
|
@@ -14920,22 +15595,27 @@ function buildArchiveHooks(opts) {
|
|
|
14920
15595
|
emitProposalApproved,
|
|
14921
15596
|
emitProposalCreated,
|
|
14922
15597
|
emitProposalRejected,
|
|
15598
|
+
explicitFindingsCount,
|
|
14923
15599
|
extractHighlights,
|
|
14924
15600
|
extractTitlePrefix,
|
|
14925
15601
|
getAvailableSlots,
|
|
14926
15602
|
getDefaultConfig,
|
|
14927
15603
|
getPerStateCount,
|
|
14928
15604
|
indexSessionDirectory,
|
|
15605
|
+
isCheckTimeoutError,
|
|
14929
15606
|
isEligible,
|
|
14930
15607
|
isSummaryEnabled,
|
|
14931
15608
|
launchTUI,
|
|
14932
15609
|
loadPublishedIndex,
|
|
15610
|
+
makeBackendResolver,
|
|
14933
15611
|
migrateAgentConfig,
|
|
14934
15612
|
normalizeFts5Query,
|
|
15613
|
+
normalizeHarnessCommand,
|
|
14935
15614
|
normalizeLocalModel,
|
|
14936
15615
|
openSearchIndex,
|
|
14937
15616
|
promote,
|
|
14938
15617
|
reconcile,
|
|
15618
|
+
recoverFindingsCount,
|
|
14939
15619
|
reindexFromArchive,
|
|
14940
15620
|
renderAnalysisComment,
|
|
14941
15621
|
renderLlmSummaryMarkdown,
|
|
@@ -14945,9 +15625,11 @@ function buildArchiveHooks(opts) {
|
|
|
14945
15625
|
routeIssue,
|
|
14946
15626
|
routingWarnings,
|
|
14947
15627
|
runGate,
|
|
15628
|
+
runHarnessCheck,
|
|
14948
15629
|
savePublishedIndex,
|
|
14949
15630
|
searchIndexPath,
|
|
14950
15631
|
selectCandidates,
|
|
15632
|
+
selectTasks,
|
|
14951
15633
|
sortCandidates,
|
|
14952
15634
|
summarizeArchivedSession,
|
|
14953
15635
|
syncMain,
|