@harness-engineering/orchestrator 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +650 -5
- package/dist/index.d.ts +650 -5
- package/dist/index.js +787 -216
- package/dist/index.mjs +823 -267
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -2209,11 +2209,10 @@ var WorkflowLoader = class {
|
|
|
2209
2209
|
};
|
|
2210
2210
|
|
|
2211
2211
|
// src/tracker/adapters/roadmap.ts
|
|
2212
|
-
import * as fs8 from "fs/promises";
|
|
2213
2212
|
import { createHash as createHash2 } from "crypto";
|
|
2214
2213
|
import {
|
|
2215
|
-
|
|
2216
|
-
|
|
2214
|
+
resolveRoadmapStoreForFile,
|
|
2215
|
+
applyRoadmapDiff,
|
|
2217
2216
|
claim as claimFeature,
|
|
2218
2217
|
setStatus as setFeatureStatus,
|
|
2219
2218
|
isClaimableBy
|
|
@@ -2249,8 +2248,7 @@ var RoadmapTrackerAdapter = class {
|
|
|
2249
2248
|
async fetchIssuesByStates(stateNames) {
|
|
2250
2249
|
try {
|
|
2251
2250
|
if (!this.config.filePath) return Err3(new Error("Missing filePath"));
|
|
2252
|
-
const
|
|
2253
|
-
const roadmapResult = parseRoadmap(content);
|
|
2251
|
+
const roadmapResult = await this.loadRoadmap();
|
|
2254
2252
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2255
2253
|
const issues = [];
|
|
2256
2254
|
for (const milestone of roadmapResult.value.milestones) {
|
|
@@ -2281,17 +2279,19 @@ var RoadmapTrackerAdapter = class {
|
|
|
2281
2279
|
if (!terminal) {
|
|
2282
2280
|
return Err3(new Error("Tracker config has no terminalStates; cannot mark complete"));
|
|
2283
2281
|
}
|
|
2284
|
-
const
|
|
2285
|
-
const roadmapResult =
|
|
2282
|
+
const store = this.resolveStore();
|
|
2283
|
+
const roadmapResult = await store.load();
|
|
2286
2284
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2287
2285
|
const roadmap = roadmapResult.value;
|
|
2286
|
+
const before = structuredClone(roadmap);
|
|
2288
2287
|
const target = this.findFeatureById(roadmap.milestones, issueId);
|
|
2289
2288
|
if (!target) return Ok4(void 0);
|
|
2290
2289
|
const normalizedTerminal = this.config.terminalStates.map((s) => s.toLowerCase());
|
|
2291
2290
|
if (normalizedTerminal.includes(target.status.toLowerCase())) return Ok4(void 0);
|
|
2292
2291
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2293
2292
|
setFeatureStatus(roadmap, target, terminal, now.slice(0, 10));
|
|
2294
|
-
await
|
|
2293
|
+
const persisted = await applyRoadmapDiff(store, before, roadmap);
|
|
2294
|
+
if (!persisted.ok) return persisted;
|
|
2295
2295
|
return Ok4(void 0);
|
|
2296
2296
|
} catch (error) {
|
|
2297
2297
|
return Err3(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -2305,10 +2305,11 @@ var RoadmapTrackerAdapter = class {
|
|
|
2305
2305
|
async claimIssue(issueId, orchestratorId) {
|
|
2306
2306
|
try {
|
|
2307
2307
|
if (!this.config.filePath) return Err3(new Error("Missing filePath"));
|
|
2308
|
-
const
|
|
2309
|
-
const roadmapResult =
|
|
2308
|
+
const store = this.resolveStore();
|
|
2309
|
+
const roadmapResult = await store.load();
|
|
2310
2310
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2311
2311
|
const roadmap = roadmapResult.value;
|
|
2312
|
+
const before = structuredClone(roadmap);
|
|
2312
2313
|
const target = this.findFeatureById(roadmap.milestones, issueId);
|
|
2313
2314
|
if (!target) return Ok4(void 0);
|
|
2314
2315
|
if (!isClaimableBy(target, orchestratorId)) {
|
|
@@ -2320,7 +2321,8 @@ var RoadmapTrackerAdapter = class {
|
|
|
2320
2321
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2321
2322
|
claimFeature(roadmap, target, orchestratorId, now.slice(0, 10));
|
|
2322
2323
|
target.updatedAt = now;
|
|
2323
|
-
await
|
|
2324
|
+
const persisted = await applyRoadmapDiff(store, before, roadmap);
|
|
2325
|
+
if (!persisted.ok) return persisted;
|
|
2324
2326
|
return Ok4(void 0);
|
|
2325
2327
|
} catch (error) {
|
|
2326
2328
|
return Err3(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -2337,10 +2339,11 @@ var RoadmapTrackerAdapter = class {
|
|
|
2337
2339
|
if (!activeState) {
|
|
2338
2340
|
return Err3(new Error("Tracker config has no activeStates; cannot release"));
|
|
2339
2341
|
}
|
|
2340
|
-
const
|
|
2341
|
-
const roadmapResult =
|
|
2342
|
+
const store = this.resolveStore();
|
|
2343
|
+
const roadmapResult = await store.load();
|
|
2342
2344
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2343
2345
|
const roadmap = roadmapResult.value;
|
|
2346
|
+
const before = structuredClone(roadmap);
|
|
2344
2347
|
const target = this.findFeatureById(roadmap.milestones, issueId);
|
|
2345
2348
|
if (!target) return Ok4(void 0);
|
|
2346
2349
|
if (this.config.activeStates.includes(target.status) && target.assignee === null) {
|
|
@@ -2349,12 +2352,26 @@ var RoadmapTrackerAdapter = class {
|
|
|
2349
2352
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2350
2353
|
setFeatureStatus(roadmap, target, activeState, now.slice(0, 10));
|
|
2351
2354
|
target.updatedAt = null;
|
|
2352
|
-
await
|
|
2355
|
+
const persisted = await applyRoadmapDiff(store, before, roadmap);
|
|
2356
|
+
if (!persisted.ok) return persisted;
|
|
2353
2357
|
return Ok4(void 0);
|
|
2354
2358
|
} catch (error) {
|
|
2355
2359
|
return Err3(error instanceof Error ? error : new Error(String(error)));
|
|
2356
2360
|
}
|
|
2357
2361
|
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Resolve the roadmap store anchored on the configured aggregate file path.
|
|
2364
|
+
* The shard backend is chosen when a sibling `roadmap.d/` exists next to the
|
|
2365
|
+
* file; otherwise the monolith file is read/written whole. Callers guard
|
|
2366
|
+
* `this.config.filePath` before invoking.
|
|
2367
|
+
*/
|
|
2368
|
+
resolveStore() {
|
|
2369
|
+
return resolveRoadmapStoreForFile({ roadmapPath: this.config.filePath });
|
|
2370
|
+
}
|
|
2371
|
+
/** Load the roadmap via the store (sharded or monolith), returning a Result. */
|
|
2372
|
+
loadRoadmap() {
|
|
2373
|
+
return this.resolveStore().load();
|
|
2374
|
+
}
|
|
2358
2375
|
findFeatureById(milestones, issueId) {
|
|
2359
2376
|
for (const milestone of milestones) {
|
|
2360
2377
|
for (const feature of milestone.features) {
|
|
@@ -2371,8 +2388,7 @@ var RoadmapTrackerAdapter = class {
|
|
|
2371
2388
|
async fetchIssueStatesByIds(issueIds) {
|
|
2372
2389
|
try {
|
|
2373
2390
|
if (!this.config.filePath) return Err3(new Error("Missing filePath"));
|
|
2374
|
-
const
|
|
2375
|
-
const roadmapResult = parseRoadmap(content);
|
|
2391
|
+
const roadmapResult = await this.loadRoadmap();
|
|
2376
2392
|
if (!roadmapResult.ok) return roadmapResult;
|
|
2377
2393
|
const issueMap = /* @__PURE__ */ new Map();
|
|
2378
2394
|
for (const milestone of roadmapResult.value.milestones) {
|
|
@@ -2427,7 +2443,57 @@ var RoadmapTrackerAdapter = class {
|
|
|
2427
2443
|
};
|
|
2428
2444
|
|
|
2429
2445
|
// src/tracker/extensions/linear.ts
|
|
2430
|
-
import { Ok as Ok5 } from "@harness-engineering/types";
|
|
2446
|
+
import { Ok as Ok5, Err as Err4 } from "@harness-engineering/types";
|
|
2447
|
+
var LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
|
|
2448
|
+
var LinearGraphQLClient = class {
|
|
2449
|
+
apiKey;
|
|
2450
|
+
endpoint;
|
|
2451
|
+
fetchFn;
|
|
2452
|
+
constructor(opts) {
|
|
2453
|
+
this.apiKey = opts.apiKey;
|
|
2454
|
+
this.endpoint = opts.endpoint ?? LINEAR_GRAPHQL_ENDPOINT;
|
|
2455
|
+
this.fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
2456
|
+
}
|
|
2457
|
+
async query(query, variables) {
|
|
2458
|
+
let res;
|
|
2459
|
+
try {
|
|
2460
|
+
res = await this.fetchFn(this.endpoint, {
|
|
2461
|
+
method: "POST",
|
|
2462
|
+
headers: {
|
|
2463
|
+
Authorization: this.apiKey,
|
|
2464
|
+
"Content-Type": "application/json"
|
|
2465
|
+
},
|
|
2466
|
+
body: JSON.stringify({ query, variables: variables ?? {} })
|
|
2467
|
+
});
|
|
2468
|
+
} catch (err) {
|
|
2469
|
+
return Err4(
|
|
2470
|
+
new Error(
|
|
2471
|
+
`Linear GraphQL request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2472
|
+
)
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
if (!res.ok) {
|
|
2476
|
+
const body = await res.text().catch(() => "");
|
|
2477
|
+
const detail = body ? `: ${body.slice(0, 500)}` : "";
|
|
2478
|
+
return Err4(new Error(`Linear GraphQL HTTP ${res.status}${detail}`));
|
|
2479
|
+
}
|
|
2480
|
+
let envelope;
|
|
2481
|
+
try {
|
|
2482
|
+
envelope = await res.json();
|
|
2483
|
+
} catch (err) {
|
|
2484
|
+
return Err4(
|
|
2485
|
+
new Error(
|
|
2486
|
+
`Linear GraphQL response was not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
2487
|
+
)
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2490
|
+
if (envelope.errors && envelope.errors.length > 0) {
|
|
2491
|
+
const message = envelope.errors.map((e) => e.message ?? "unknown error").join("; ");
|
|
2492
|
+
return Err4(new Error(`Linear GraphQL error: ${message}`));
|
|
2493
|
+
}
|
|
2494
|
+
return Ok5(envelope.data ?? {});
|
|
2495
|
+
}
|
|
2496
|
+
};
|
|
2431
2497
|
var LinearGraphQLStub = class {
|
|
2432
2498
|
async query(query, _variables) {
|
|
2433
2499
|
console.log("Linear GraphQL query (stub):", query);
|
|
@@ -2436,11 +2502,11 @@ var LinearGraphQLStub = class {
|
|
|
2436
2502
|
};
|
|
2437
2503
|
|
|
2438
2504
|
// src/workspace/manager.ts
|
|
2439
|
-
import * as
|
|
2505
|
+
import * as fs8 from "fs/promises";
|
|
2440
2506
|
import * as path8 from "path";
|
|
2441
2507
|
import { execFile as execFile2 } from "child_process";
|
|
2442
2508
|
import { promisify as promisify2 } from "util";
|
|
2443
|
-
import { Ok as Ok6, Err as
|
|
2509
|
+
import { Ok as Ok6, Err as Err5 } from "@harness-engineering/types";
|
|
2444
2510
|
var WorkspaceManager = class _WorkspaceManager {
|
|
2445
2511
|
config;
|
|
2446
2512
|
/** Absolute path to the git repository root (resolved lazily). */
|
|
@@ -2476,7 +2542,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2476
2542
|
async getRepoRoot() {
|
|
2477
2543
|
if (this.repoRoot) return this.repoRoot;
|
|
2478
2544
|
const root = path8.resolve(this.config.root);
|
|
2479
|
-
await
|
|
2545
|
+
await fs8.mkdir(root, { recursive: true });
|
|
2480
2546
|
const stdout = await this.git(["rev-parse", "--show-toplevel"], root);
|
|
2481
2547
|
this.repoRoot = stdout.trim();
|
|
2482
2548
|
return this.repoRoot;
|
|
@@ -2489,21 +2555,21 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2489
2555
|
try {
|
|
2490
2556
|
const workspacePath = path8.resolve(this.resolvePath(identifier));
|
|
2491
2557
|
try {
|
|
2492
|
-
await
|
|
2558
|
+
await fs8.access(path8.join(workspacePath, ".git"));
|
|
2493
2559
|
const repoRoot2 = await this.getRepoRoot();
|
|
2494
2560
|
try {
|
|
2495
2561
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
|
|
2496
2562
|
} catch {
|
|
2497
|
-
await
|
|
2563
|
+
await fs8.rm(workspacePath, { recursive: true, force: true });
|
|
2498
2564
|
}
|
|
2499
2565
|
} catch {
|
|
2500
2566
|
try {
|
|
2501
|
-
await
|
|
2567
|
+
await fs8.access(workspacePath);
|
|
2502
2568
|
const repoRoot2 = await this.getRepoRoot();
|
|
2503
2569
|
try {
|
|
2504
2570
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot2);
|
|
2505
2571
|
} catch {
|
|
2506
|
-
await
|
|
2572
|
+
await fs8.rm(workspacePath, { recursive: true, force: true });
|
|
2507
2573
|
}
|
|
2508
2574
|
} catch {
|
|
2509
2575
|
}
|
|
@@ -2515,7 +2581,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2515
2581
|
await this.seedWorkspace(workspacePath, repoRoot);
|
|
2516
2582
|
return Ok6(workspacePath);
|
|
2517
2583
|
} catch (error) {
|
|
2518
|
-
return
|
|
2584
|
+
return Err5(error instanceof Error ? error : new Error(String(error)));
|
|
2519
2585
|
}
|
|
2520
2586
|
}
|
|
2521
2587
|
/**
|
|
@@ -2556,13 +2622,13 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2556
2622
|
}
|
|
2557
2623
|
const src = path8.join(repoRoot, rel);
|
|
2558
2624
|
try {
|
|
2559
|
-
await
|
|
2625
|
+
await fs8.access(src);
|
|
2560
2626
|
} catch {
|
|
2561
2627
|
continue;
|
|
2562
2628
|
}
|
|
2563
2629
|
const dest = path8.join(workspacePath, rel);
|
|
2564
2630
|
try {
|
|
2565
|
-
await
|
|
2631
|
+
await fs8.cp(src, dest, { recursive: true, force: true });
|
|
2566
2632
|
} catch {
|
|
2567
2633
|
}
|
|
2568
2634
|
}
|
|
@@ -2638,7 +2704,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2638
2704
|
async exists(identifier) {
|
|
2639
2705
|
try {
|
|
2640
2706
|
const workspacePath = this.resolvePath(identifier);
|
|
2641
|
-
await
|
|
2707
|
+
await fs8.access(workspacePath);
|
|
2642
2708
|
return true;
|
|
2643
2709
|
} catch {
|
|
2644
2710
|
return false;
|
|
@@ -2653,7 +2719,7 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2653
2719
|
try {
|
|
2654
2720
|
const workspacePath = path8.resolve(this.resolvePath(identifier));
|
|
2655
2721
|
try {
|
|
2656
|
-
await
|
|
2722
|
+
await fs8.access(path8.join(workspacePath, ".git"));
|
|
2657
2723
|
} catch {
|
|
2658
2724
|
return null;
|
|
2659
2725
|
}
|
|
@@ -2764,18 +2830,18 @@ var WorkspaceManager = class _WorkspaceManager {
|
|
|
2764
2830
|
const repoRoot = await this.getRepoRoot();
|
|
2765
2831
|
await this.git(["worktree", "remove", "--force", workspacePath], repoRoot);
|
|
2766
2832
|
} catch {
|
|
2767
|
-
await
|
|
2833
|
+
await fs8.rm(workspacePath, { recursive: true, force: true });
|
|
2768
2834
|
}
|
|
2769
2835
|
return Ok6(void 0);
|
|
2770
2836
|
} catch (error) {
|
|
2771
|
-
return
|
|
2837
|
+
return Err5(error instanceof Error ? error : new Error(String(error)));
|
|
2772
2838
|
}
|
|
2773
2839
|
}
|
|
2774
2840
|
};
|
|
2775
2841
|
|
|
2776
2842
|
// src/workspace/hooks.ts
|
|
2777
2843
|
import { spawn } from "child_process";
|
|
2778
|
-
import { Ok as Ok7, Err as
|
|
2844
|
+
import { Ok as Ok7, Err as Err6 } from "@harness-engineering/types";
|
|
2779
2845
|
var WorkspaceHooks = class {
|
|
2780
2846
|
config;
|
|
2781
2847
|
constructor(config) {
|
|
@@ -2802,19 +2868,19 @@ var WorkspaceHooks = class {
|
|
|
2802
2868
|
});
|
|
2803
2869
|
const timeout = setTimeout(() => {
|
|
2804
2870
|
child.kill();
|
|
2805
|
-
resolve8(
|
|
2871
|
+
resolve8(Err6(new Error(`Hook ${hookName} timed out after ${this.config.timeoutMs}ms`)));
|
|
2806
2872
|
}, this.config.timeoutMs);
|
|
2807
2873
|
child.on("exit", (code) => {
|
|
2808
2874
|
clearTimeout(timeout);
|
|
2809
2875
|
if (code === 0) {
|
|
2810
2876
|
resolve8(Ok7(void 0));
|
|
2811
2877
|
} else {
|
|
2812
|
-
resolve8(
|
|
2878
|
+
resolve8(Err6(new Error(`Hook ${hookName} failed with exit code ${code}`)));
|
|
2813
2879
|
}
|
|
2814
2880
|
});
|
|
2815
2881
|
child.on("error", (error) => {
|
|
2816
2882
|
clearTimeout(timeout);
|
|
2817
|
-
resolve8(
|
|
2883
|
+
resolve8(Err6(error));
|
|
2818
2884
|
});
|
|
2819
2885
|
});
|
|
2820
2886
|
}
|
|
@@ -3494,7 +3560,7 @@ import {
|
|
|
3494
3560
|
} from "@harness-engineering/core";
|
|
3495
3561
|
|
|
3496
3562
|
// src/tracker/adapters/github-issues-issue-tracker.ts
|
|
3497
|
-
import { Ok as Ok9, Err as
|
|
3563
|
+
import { Ok as Ok9, Err as Err7 } from "@harness-engineering/types";
|
|
3498
3564
|
var GitHubIssuesIssueTrackerAdapter = class {
|
|
3499
3565
|
client;
|
|
3500
3566
|
config;
|
|
@@ -3509,12 +3575,12 @@ var GitHubIssuesIssueTrackerAdapter = class {
|
|
|
3509
3575
|
const r = await this.client.fetchByStatus(
|
|
3510
3576
|
stateNames
|
|
3511
3577
|
);
|
|
3512
|
-
if (!r.ok) return
|
|
3578
|
+
if (!r.ok) return Err7(r.error);
|
|
3513
3579
|
return Ok9(r.value.map((f) => this.mapTrackedToIssue(f)));
|
|
3514
3580
|
}
|
|
3515
3581
|
async fetchIssueStatesByIds(issueIds) {
|
|
3516
3582
|
const r = await this.client.fetchAll();
|
|
3517
|
-
if (!r.ok) return
|
|
3583
|
+
if (!r.ok) return Err7(r.error);
|
|
3518
3584
|
const wanted = new Set(issueIds);
|
|
3519
3585
|
const out = /* @__PURE__ */ new Map();
|
|
3520
3586
|
for (const f of r.value.features) {
|
|
@@ -3524,17 +3590,17 @@ var GitHubIssuesIssueTrackerAdapter = class {
|
|
|
3524
3590
|
}
|
|
3525
3591
|
async claimIssue(issueId, orchestratorId) {
|
|
3526
3592
|
const r = await this.client.claim(issueId, orchestratorId);
|
|
3527
|
-
if (!r.ok) return
|
|
3593
|
+
if (!r.ok) return Err7(r.error);
|
|
3528
3594
|
return Ok9(void 0);
|
|
3529
3595
|
}
|
|
3530
3596
|
async releaseIssue(issueId) {
|
|
3531
3597
|
const r = await this.client.release(issueId);
|
|
3532
|
-
if (!r.ok) return
|
|
3598
|
+
if (!r.ok) return Err7(r.error);
|
|
3533
3599
|
return Ok9(void 0);
|
|
3534
3600
|
}
|
|
3535
3601
|
async markIssueComplete(issueId) {
|
|
3536
3602
|
const r = await this.client.complete(issueId);
|
|
3537
|
-
if (!r.ok) return
|
|
3603
|
+
if (!r.ok) return Err7(r.error);
|
|
3538
3604
|
return Ok9(void 0);
|
|
3539
3605
|
}
|
|
3540
3606
|
/**
|
|
@@ -4185,14 +4251,14 @@ import * as readline from "readline";
|
|
|
4185
4251
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4186
4252
|
import {
|
|
4187
4253
|
Ok as Ok10,
|
|
4188
|
-
Err as
|
|
4254
|
+
Err as Err8
|
|
4189
4255
|
} from "@harness-engineering/types";
|
|
4190
4256
|
function resolveExitCode(code, command, resolve8) {
|
|
4191
4257
|
if (code === 0) {
|
|
4192
4258
|
resolve8(Ok10(void 0));
|
|
4193
4259
|
} else {
|
|
4194
4260
|
resolve8(
|
|
4195
|
-
|
|
4261
|
+
Err8({
|
|
4196
4262
|
category: "agent_not_found",
|
|
4197
4263
|
message: `Claude command '${command}' not found or failed`
|
|
4198
4264
|
})
|
|
@@ -4200,7 +4266,7 @@ function resolveExitCode(code, command, resolve8) {
|
|
|
4200
4266
|
}
|
|
4201
4267
|
}
|
|
4202
4268
|
function resolveSpawnError(command, resolve8) {
|
|
4203
|
-
resolve8(
|
|
4269
|
+
resolve8(Err8({ category: "agent_not_found", message: `Claude command '${command}' not found` }));
|
|
4204
4270
|
}
|
|
4205
4271
|
var JUST_PAST_GRACE_MS = 5 * 6e4;
|
|
4206
4272
|
var PRIMARY_LIMIT_RE = /You[\u2019']ve hit your limit.*resets\s+(\d{1,2}(?::\d{2})?\s*(?:am|pm))\s*\(([^)]+)\)/i;
|
|
@@ -4550,7 +4616,7 @@ var ClaudeBackend = class {
|
|
|
4550
4616
|
import Anthropic from "@anthropic-ai/sdk";
|
|
4551
4617
|
import {
|
|
4552
4618
|
Ok as Ok11,
|
|
4553
|
-
Err as
|
|
4619
|
+
Err as Err9
|
|
4554
4620
|
} from "@harness-engineering/types";
|
|
4555
4621
|
import { AnthropicCacheAdapter } from "@harness-engineering/core";
|
|
4556
4622
|
var AnthropicBackend = class {
|
|
@@ -4569,7 +4635,7 @@ var AnthropicBackend = class {
|
|
|
4569
4635
|
}
|
|
4570
4636
|
async startSession(params) {
|
|
4571
4637
|
if (!this.config.apiKey) {
|
|
4572
|
-
return
|
|
4638
|
+
return Err9({
|
|
4573
4639
|
category: "agent_not_found",
|
|
4574
4640
|
message: "ANTHROPIC_API_KEY is not set"
|
|
4575
4641
|
});
|
|
@@ -4650,7 +4716,7 @@ var AnthropicBackend = class {
|
|
|
4650
4716
|
}
|
|
4651
4717
|
async healthCheck() {
|
|
4652
4718
|
if (!this.config.apiKey) {
|
|
4653
|
-
return
|
|
4719
|
+
return Err9({
|
|
4654
4720
|
category: "response_error",
|
|
4655
4721
|
message: "ANTHROPIC_API_KEY is not set"
|
|
4656
4722
|
});
|
|
@@ -4663,7 +4729,7 @@ var AnthropicBackend = class {
|
|
|
4663
4729
|
import OpenAI from "openai";
|
|
4664
4730
|
import {
|
|
4665
4731
|
Ok as Ok12,
|
|
4666
|
-
Err as
|
|
4732
|
+
Err as Err10
|
|
4667
4733
|
} from "@harness-engineering/types";
|
|
4668
4734
|
import { OpenAICacheAdapter } from "@harness-engineering/core";
|
|
4669
4735
|
var OpenAIBackend = class {
|
|
@@ -4681,7 +4747,7 @@ var OpenAIBackend = class {
|
|
|
4681
4747
|
}
|
|
4682
4748
|
async startSession(params) {
|
|
4683
4749
|
if (!this.config.apiKey) {
|
|
4684
|
-
return
|
|
4750
|
+
return Err10({
|
|
4685
4751
|
category: "agent_not_found",
|
|
4686
4752
|
message: "OPENAI_API_KEY is not set"
|
|
4687
4753
|
});
|
|
@@ -4776,7 +4842,7 @@ var OpenAIBackend = class {
|
|
|
4776
4842
|
await this.client.models.list();
|
|
4777
4843
|
return Ok12(void 0);
|
|
4778
4844
|
} catch (err) {
|
|
4779
|
-
return
|
|
4845
|
+
return Err10({
|
|
4780
4846
|
category: "response_error",
|
|
4781
4847
|
message: err instanceof Error ? err.message : "OpenAI health check failed"
|
|
4782
4848
|
});
|
|
@@ -4788,7 +4854,7 @@ var OpenAIBackend = class {
|
|
|
4788
4854
|
import { GoogleGenAI } from "@google/genai";
|
|
4789
4855
|
import {
|
|
4790
4856
|
Ok as Ok13,
|
|
4791
|
-
Err as
|
|
4857
|
+
Err as Err11
|
|
4792
4858
|
} from "@harness-engineering/types";
|
|
4793
4859
|
import { GeminiCacheAdapter } from "@harness-engineering/core";
|
|
4794
4860
|
var GeminiBackend = class {
|
|
@@ -4804,7 +4870,7 @@ var GeminiBackend = class {
|
|
|
4804
4870
|
}
|
|
4805
4871
|
async startSession(params) {
|
|
4806
4872
|
if (!this.config.apiKey) {
|
|
4807
|
-
return
|
|
4873
|
+
return Err11({
|
|
4808
4874
|
category: "agent_not_found",
|
|
4809
4875
|
message: "GEMINI_API_KEY is not set"
|
|
4810
4876
|
});
|
|
@@ -4904,7 +4970,7 @@ var GeminiBackend = class {
|
|
|
4904
4970
|
}
|
|
4905
4971
|
async healthCheck() {
|
|
4906
4972
|
if (!this.config.apiKey) {
|
|
4907
|
-
return
|
|
4973
|
+
return Err11({
|
|
4908
4974
|
category: "agent_not_found",
|
|
4909
4975
|
message: "GEMINI_API_KEY is not set"
|
|
4910
4976
|
});
|
|
@@ -4913,7 +4979,7 @@ var GeminiBackend = class {
|
|
|
4913
4979
|
new GoogleGenAI({ apiKey: this.config.apiKey });
|
|
4914
4980
|
return Ok13(void 0);
|
|
4915
4981
|
} catch (err) {
|
|
4916
|
-
return
|
|
4982
|
+
return Err11({
|
|
4917
4983
|
category: "response_error",
|
|
4918
4984
|
message: err instanceof Error ? err.message : "Gemini health check failed"
|
|
4919
4985
|
});
|
|
@@ -4925,7 +4991,7 @@ var GeminiBackend = class {
|
|
|
4925
4991
|
import OpenAI2 from "openai";
|
|
4926
4992
|
import {
|
|
4927
4993
|
Ok as Ok14,
|
|
4928
|
-
Err as
|
|
4994
|
+
Err as Err12
|
|
4929
4995
|
} from "@harness-engineering/types";
|
|
4930
4996
|
var DEFAULT_TIMEOUT_MS = 9e4;
|
|
4931
4997
|
var LocalBackend = class {
|
|
@@ -4952,7 +5018,7 @@ var LocalBackend = class {
|
|
|
4952
5018
|
if (this.getModel) {
|
|
4953
5019
|
const candidate = this.getModel();
|
|
4954
5020
|
if (candidate === null) {
|
|
4955
|
-
return
|
|
5021
|
+
return Err12({
|
|
4956
5022
|
category: "agent_not_found",
|
|
4957
5023
|
message: "No local model available; check dashboard for details."
|
|
4958
5024
|
});
|
|
@@ -5041,7 +5107,7 @@ var LocalBackend = class {
|
|
|
5041
5107
|
await this.client.models.list();
|
|
5042
5108
|
return Ok14(void 0);
|
|
5043
5109
|
} catch (err) {
|
|
5044
|
-
return
|
|
5110
|
+
return Err12({
|
|
5045
5111
|
category: "response_error",
|
|
5046
5112
|
message: err instanceof Error ? err.message : "Local backend health check failed"
|
|
5047
5113
|
});
|
|
@@ -5053,7 +5119,7 @@ var LocalBackend = class {
|
|
|
5053
5119
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5054
5120
|
import {
|
|
5055
5121
|
Ok as Ok15,
|
|
5056
|
-
Err as
|
|
5122
|
+
Err as Err13
|
|
5057
5123
|
} from "@harness-engineering/types";
|
|
5058
5124
|
var SILENT_EVENTS = /* @__PURE__ */ new Set([
|
|
5059
5125
|
"turn_end",
|
|
@@ -5171,7 +5237,7 @@ var PiBackend = class {
|
|
|
5171
5237
|
if (this.config.getModel) {
|
|
5172
5238
|
const candidate = this.config.getModel();
|
|
5173
5239
|
if (candidate === null) {
|
|
5174
|
-
return
|
|
5240
|
+
return Err13({
|
|
5175
5241
|
category: "agent_not_found",
|
|
5176
5242
|
message: "No local model available; check dashboard for details."
|
|
5177
5243
|
});
|
|
@@ -5201,7 +5267,7 @@ var PiBackend = class {
|
|
|
5201
5267
|
};
|
|
5202
5268
|
return Ok15(session);
|
|
5203
5269
|
} catch (err) {
|
|
5204
|
-
return
|
|
5270
|
+
return Err13({
|
|
5205
5271
|
category: "response_error",
|
|
5206
5272
|
message: `Failed to create pi session: ${err instanceof Error ? err.message : String(err)}`
|
|
5207
5273
|
});
|
|
@@ -5338,7 +5404,7 @@ var PiBackend = class {
|
|
|
5338
5404
|
await import("@earendil-works/pi-coding-agent");
|
|
5339
5405
|
return Ok15(void 0);
|
|
5340
5406
|
} catch (err) {
|
|
5341
|
-
return
|
|
5407
|
+
return Err13({
|
|
5342
5408
|
category: "agent_not_found",
|
|
5343
5409
|
message: `Pi SDK not available: ${err instanceof Error ? err.message : String(err)}`
|
|
5344
5410
|
});
|
|
@@ -5350,7 +5416,7 @@ var PiBackend = class {
|
|
|
5350
5416
|
import { spawn as spawn3 } from "child_process";
|
|
5351
5417
|
import {
|
|
5352
5418
|
Ok as Ok16,
|
|
5353
|
-
Err as
|
|
5419
|
+
Err as Err14
|
|
5354
5420
|
} from "@harness-engineering/types";
|
|
5355
5421
|
var DEFAULT_TIMEOUT_MS2 = 9e4;
|
|
5356
5422
|
var FORBIDDEN_HOST_CHARS = /[;&|`$()\n\r<>]/;
|
|
@@ -5497,7 +5563,7 @@ var SshBackend = class {
|
|
|
5497
5563
|
});
|
|
5498
5564
|
} catch (err) {
|
|
5499
5565
|
resolve8(
|
|
5500
|
-
|
|
5566
|
+
Err14({
|
|
5501
5567
|
category: "agent_not_found",
|
|
5502
5568
|
message: err instanceof Error ? err.message : "failed to spawn ssh"
|
|
5503
5569
|
})
|
|
@@ -5520,7 +5586,7 @@ var SshBackend = class {
|
|
|
5520
5586
|
resolve8(Ok16(void 0));
|
|
5521
5587
|
} else {
|
|
5522
5588
|
resolve8(
|
|
5523
|
-
|
|
5589
|
+
Err14({
|
|
5524
5590
|
category: "agent_not_found",
|
|
5525
5591
|
message: `ssh health check failed (exit=${code ?? "null"}): ${stderr.slice(0, 500)}`
|
|
5526
5592
|
})
|
|
@@ -5529,7 +5595,7 @@ var SshBackend = class {
|
|
|
5529
5595
|
});
|
|
5530
5596
|
child.on("error", (err) => {
|
|
5531
5597
|
clearTimeout(timer);
|
|
5532
|
-
resolve8(
|
|
5598
|
+
resolve8(Err14({ category: "agent_not_found", message: err.message }));
|
|
5533
5599
|
});
|
|
5534
5600
|
});
|
|
5535
5601
|
}
|
|
@@ -5591,7 +5657,7 @@ function waitForExit(child) {
|
|
|
5591
5657
|
import { spawn as spawn4 } from "child_process";
|
|
5592
5658
|
import {
|
|
5593
5659
|
Ok as Ok17,
|
|
5594
|
-
Err as
|
|
5660
|
+
Err as Err15
|
|
5595
5661
|
} from "@harness-engineering/types";
|
|
5596
5662
|
var ServerlessBackend = class {
|
|
5597
5663
|
handles = /* @__PURE__ */ new Map();
|
|
@@ -5690,7 +5756,7 @@ var OciServerlessBackend = class extends ServerlessBackend {
|
|
|
5690
5756
|
if (!result.ok) return result;
|
|
5691
5757
|
const id = result.value.trim().split(/\s+/)[0] ?? "";
|
|
5692
5758
|
if (!id) {
|
|
5693
|
-
return
|
|
5759
|
+
return Err15({
|
|
5694
5760
|
category: "response_error",
|
|
5695
5761
|
message: "OciServerlessBackend: empty container id from runtime"
|
|
5696
5762
|
});
|
|
@@ -5750,7 +5816,7 @@ var OciServerlessBackend = class extends ServerlessBackend {
|
|
|
5750
5816
|
}
|
|
5751
5817
|
async teardown(handle) {
|
|
5752
5818
|
if (handle.adapter !== this.name) {
|
|
5753
|
-
return
|
|
5819
|
+
return Err15({
|
|
5754
5820
|
category: "response_error",
|
|
5755
5821
|
message: `handle adapter mismatch: got '${handle.adapter}', expected '${this.name}'`
|
|
5756
5822
|
});
|
|
@@ -5781,7 +5847,7 @@ var OciServerlessBackend = class extends ServerlessBackend {
|
|
|
5781
5847
|
});
|
|
5782
5848
|
} catch (err) {
|
|
5783
5849
|
resolve8(
|
|
5784
|
-
|
|
5850
|
+
Err15({
|
|
5785
5851
|
category: "agent_not_found",
|
|
5786
5852
|
message: err instanceof Error ? err.message : "failed to spawn runtime"
|
|
5787
5853
|
})
|
|
@@ -5808,7 +5874,7 @@ var OciServerlessBackend = class extends ServerlessBackend {
|
|
|
5808
5874
|
resolve8(Ok17(stdout));
|
|
5809
5875
|
} else {
|
|
5810
5876
|
resolve8(
|
|
5811
|
-
|
|
5877
|
+
Err15({
|
|
5812
5878
|
category: "response_error",
|
|
5813
5879
|
message: `runtime '${binary} ${args.join(" ")}' exited ${code ?? "null"}: ${stderr.slice(0, 500)}`
|
|
5814
5880
|
})
|
|
@@ -5817,7 +5883,7 @@ var OciServerlessBackend = class extends ServerlessBackend {
|
|
|
5817
5883
|
});
|
|
5818
5884
|
child.on("error", (err) => {
|
|
5819
5885
|
clearTimeout(timer);
|
|
5820
|
-
resolve8(
|
|
5886
|
+
resolve8(Err15({ category: "agent_not_found", message: err.message }));
|
|
5821
5887
|
});
|
|
5822
5888
|
});
|
|
5823
5889
|
}
|
|
@@ -5974,7 +6040,7 @@ function createBackend(def, options = {}) {
|
|
|
5974
6040
|
|
|
5975
6041
|
// src/agent/backends/container.ts
|
|
5976
6042
|
import {
|
|
5977
|
-
Err as
|
|
6043
|
+
Err as Err16
|
|
5978
6044
|
} from "@harness-engineering/types";
|
|
5979
6045
|
function toAgentError(message, details) {
|
|
5980
6046
|
return { category: "response_error", message, details };
|
|
@@ -6006,7 +6072,7 @@ var ContainerBackend = class {
|
|
|
6006
6072
|
}
|
|
6007
6073
|
const result = await this.secretBackend.resolveSecrets(this.secretKeys);
|
|
6008
6074
|
if (!result.ok) {
|
|
6009
|
-
return
|
|
6075
|
+
return Err16(toAgentError(`Secret resolution failed: ${result.error.message}`, result.error));
|
|
6010
6076
|
}
|
|
6011
6077
|
return { ok: true, value: result.value };
|
|
6012
6078
|
}
|
|
@@ -6031,7 +6097,7 @@ var ContainerBackend = class {
|
|
|
6031
6097
|
const createOpts = this.buildCreateOpts(params, envResult.value);
|
|
6032
6098
|
const containerResult = await this.runtime.createContainer(createOpts);
|
|
6033
6099
|
if (!containerResult.ok) {
|
|
6034
|
-
return
|
|
6100
|
+
return Err16(
|
|
6035
6101
|
toAgentError(
|
|
6036
6102
|
`Container creation failed: ${containerResult.error.message}`,
|
|
6037
6103
|
containerResult.error
|
|
@@ -6056,7 +6122,7 @@ var ContainerBackend = class {
|
|
|
6056
6122
|
this.containerHandles.delete(session.sessionId);
|
|
6057
6123
|
const removeResult = await this.runtime.removeContainer(handle);
|
|
6058
6124
|
if (!removeResult.ok) {
|
|
6059
|
-
return
|
|
6125
|
+
return Err16(
|
|
6060
6126
|
toAgentError(
|
|
6061
6127
|
`Container removal failed: ${removeResult.error.message}`,
|
|
6062
6128
|
removeResult.error
|
|
@@ -6069,7 +6135,7 @@ var ContainerBackend = class {
|
|
|
6069
6135
|
async healthCheck() {
|
|
6070
6136
|
const runtimeResult = await this.runtime.healthCheck();
|
|
6071
6137
|
if (!runtimeResult.ok) {
|
|
6072
|
-
return
|
|
6138
|
+
return Err16({
|
|
6073
6139
|
category: "agent_not_found",
|
|
6074
6140
|
message: `Container runtime unhealthy: ${runtimeResult.error.message}`,
|
|
6075
6141
|
details: runtimeResult.error
|
|
@@ -6081,7 +6147,7 @@ var ContainerBackend = class {
|
|
|
6081
6147
|
|
|
6082
6148
|
// src/agent/runtime/docker.ts
|
|
6083
6149
|
import { execFile as execFile3, spawn as spawn5 } from "child_process";
|
|
6084
|
-
import { Ok as Ok18, Err as
|
|
6150
|
+
import { Ok as Ok18, Err as Err17 } from "@harness-engineering/types";
|
|
6085
6151
|
function dockerExec(args) {
|
|
6086
6152
|
return new Promise((resolve8, reject) => {
|
|
6087
6153
|
execFile3("docker", args, (error, stdout) => {
|
|
@@ -6116,7 +6182,7 @@ var DockerRuntime = class {
|
|
|
6116
6182
|
const containerId = await dockerExec(args);
|
|
6117
6183
|
return Ok18({ containerId, runtime: this.name });
|
|
6118
6184
|
} catch (error) {
|
|
6119
|
-
return
|
|
6185
|
+
return Err17({
|
|
6120
6186
|
category: "container_create_failed",
|
|
6121
6187
|
message: `Failed to create container: ${error instanceof Error ? error.message : String(error)}`,
|
|
6122
6188
|
details: error
|
|
@@ -6162,7 +6228,7 @@ var DockerRuntime = class {
|
|
|
6162
6228
|
await dockerExec(["rm", "-f", handle.containerId]);
|
|
6163
6229
|
return Ok18(void 0);
|
|
6164
6230
|
} catch (error) {
|
|
6165
|
-
return
|
|
6231
|
+
return Err17({
|
|
6166
6232
|
category: "container_remove_failed",
|
|
6167
6233
|
message: `Failed to remove container: ${error instanceof Error ? error.message : String(error)}`,
|
|
6168
6234
|
details: error
|
|
@@ -6174,7 +6240,7 @@ var DockerRuntime = class {
|
|
|
6174
6240
|
await dockerExec(["info", "--format", "{{.ServerVersion}}"]);
|
|
6175
6241
|
return Ok18(void 0);
|
|
6176
6242
|
} catch (error) {
|
|
6177
|
-
return
|
|
6243
|
+
return Err17({
|
|
6178
6244
|
category: "runtime_not_found",
|
|
6179
6245
|
message: `Docker is not available: ${error instanceof Error ? error.message : String(error)}`,
|
|
6180
6246
|
details: error
|
|
@@ -6184,7 +6250,7 @@ var DockerRuntime = class {
|
|
|
6184
6250
|
};
|
|
6185
6251
|
|
|
6186
6252
|
// src/agent/secrets/env.ts
|
|
6187
|
-
import { Ok as Ok19, Err as
|
|
6253
|
+
import { Ok as Ok19, Err as Err18 } from "@harness-engineering/types";
|
|
6188
6254
|
var EnvSecretBackend = class {
|
|
6189
6255
|
name = "env";
|
|
6190
6256
|
async resolveSecrets(keys) {
|
|
@@ -6192,7 +6258,7 @@ var EnvSecretBackend = class {
|
|
|
6192
6258
|
for (const key of keys) {
|
|
6193
6259
|
const value = process.env[key];
|
|
6194
6260
|
if (value === void 0) {
|
|
6195
|
-
return
|
|
6261
|
+
return Err18({
|
|
6196
6262
|
category: "secret_not_found",
|
|
6197
6263
|
message: `Environment variable '${key}' is not set`,
|
|
6198
6264
|
key
|
|
@@ -6209,7 +6275,7 @@ var EnvSecretBackend = class {
|
|
|
6209
6275
|
|
|
6210
6276
|
// src/agent/secrets/onepassword.ts
|
|
6211
6277
|
import { execFile as execFile4 } from "child_process";
|
|
6212
|
-
import { Ok as Ok20, Err as
|
|
6278
|
+
import { Ok as Ok20, Err as Err19 } from "@harness-engineering/types";
|
|
6213
6279
|
function opExec(args) {
|
|
6214
6280
|
return new Promise((resolve8, reject) => {
|
|
6215
6281
|
execFile4("op", args, (error, stdout) => {
|
|
@@ -6234,7 +6300,7 @@ var OnePasswordSecretBackend = class {
|
|
|
6234
6300
|
const value = await opExec(["read", `op://${this.vault}/${key}/password`]);
|
|
6235
6301
|
secrets[key] = value;
|
|
6236
6302
|
} catch (error) {
|
|
6237
|
-
return
|
|
6303
|
+
return Err19({
|
|
6238
6304
|
category: "access_denied",
|
|
6239
6305
|
message: `Failed to read secret '${key}' from 1Password: ${error instanceof Error ? error.message : String(error)}`,
|
|
6240
6306
|
key
|
|
@@ -6248,7 +6314,7 @@ var OnePasswordSecretBackend = class {
|
|
|
6248
6314
|
await opExec(["--version"]);
|
|
6249
6315
|
return Ok20(void 0);
|
|
6250
6316
|
} catch (error) {
|
|
6251
|
-
return
|
|
6317
|
+
return Err19({
|
|
6252
6318
|
category: "provider_unavailable",
|
|
6253
6319
|
message: `1Password CLI is not available: ${error instanceof Error ? error.message : String(error)}`
|
|
6254
6320
|
});
|
|
@@ -6258,7 +6324,7 @@ var OnePasswordSecretBackend = class {
|
|
|
6258
6324
|
|
|
6259
6325
|
// src/agent/secrets/vault.ts
|
|
6260
6326
|
import { execFile as execFile5 } from "child_process";
|
|
6261
|
-
import { Ok as Ok21, Err as
|
|
6327
|
+
import { Ok as Ok21, Err as Err20 } from "@harness-engineering/types";
|
|
6262
6328
|
function vaultExec(args, env) {
|
|
6263
6329
|
return new Promise((resolve8, reject) => {
|
|
6264
6330
|
execFile5("vault", args, { env: { ...process.env, ...env } }, (error, stdout) => {
|
|
@@ -6289,11 +6355,11 @@ var VaultSecretBackend = class {
|
|
|
6289
6355
|
} catch (error) {
|
|
6290
6356
|
const msg = error instanceof Error ? error.message : String(error);
|
|
6291
6357
|
const category = error instanceof SyntaxError ? "access_denied" : "access_denied";
|
|
6292
|
-
return
|
|
6358
|
+
return Err20({ category, message: `Failed to read from Vault: ${msg}` });
|
|
6293
6359
|
}
|
|
6294
6360
|
const missing = keys.find((k) => !(k in data));
|
|
6295
6361
|
if (missing) {
|
|
6296
|
-
return
|
|
6362
|
+
return Err20({
|
|
6297
6363
|
category: "secret_not_found",
|
|
6298
6364
|
message: `Secret key '${missing}' not found in Vault path '${this.path}'`,
|
|
6299
6365
|
key: missing
|
|
@@ -6308,7 +6374,7 @@ var VaultSecretBackend = class {
|
|
|
6308
6374
|
await vaultExec(["version"]);
|
|
6309
6375
|
return Ok21(void 0);
|
|
6310
6376
|
} catch (error) {
|
|
6311
|
-
return
|
|
6377
|
+
return Err20({
|
|
6312
6378
|
category: "provider_unavailable",
|
|
6313
6379
|
message: `Vault CLI is not available: ${error instanceof Error ? error.message : String(error)}`
|
|
6314
6380
|
});
|
|
@@ -6437,6 +6503,96 @@ var OrchestratorBackendFactory = class {
|
|
|
6437
6503
|
}
|
|
6438
6504
|
};
|
|
6439
6505
|
|
|
6506
|
+
// src/agent/backend-resolver.ts
|
|
6507
|
+
function makeBackendResolver(backends) {
|
|
6508
|
+
return (backendName) => {
|
|
6509
|
+
const def = backends?.[backendName];
|
|
6510
|
+
return def ? createBackend(def) : null;
|
|
6511
|
+
};
|
|
6512
|
+
}
|
|
6513
|
+
|
|
6514
|
+
// src/maintenance/agent-dispatcher.ts
|
|
6515
|
+
function syntheticIssue(branch, skill) {
|
|
6516
|
+
return {
|
|
6517
|
+
id: `maintenance:${branch}`,
|
|
6518
|
+
identifier: branch,
|
|
6519
|
+
title: `Maintenance: ${skill}`,
|
|
6520
|
+
description: null,
|
|
6521
|
+
priority: null,
|
|
6522
|
+
state: "in_progress",
|
|
6523
|
+
branchName: branch,
|
|
6524
|
+
url: null,
|
|
6525
|
+
labels: ["maintenance"],
|
|
6526
|
+
blockedBy: [],
|
|
6527
|
+
spec: null,
|
|
6528
|
+
plans: [],
|
|
6529
|
+
createdAt: null,
|
|
6530
|
+
updatedAt: null,
|
|
6531
|
+
externalId: null
|
|
6532
|
+
};
|
|
6533
|
+
}
|
|
6534
|
+
function buildPrompt(skill, promptContext) {
|
|
6535
|
+
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.`;
|
|
6536
|
+
return promptContext ? `${promptContext}
|
|
6537
|
+
|
|
6538
|
+
${instruction}` : instruction;
|
|
6539
|
+
}
|
|
6540
|
+
function headOf(git2, cwd) {
|
|
6541
|
+
try {
|
|
6542
|
+
return git2(["rev-parse", "HEAD"], cwd) || null;
|
|
6543
|
+
} catch {
|
|
6544
|
+
return null;
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
function createAgentDispatcher(deps) {
|
|
6548
|
+
const maxTurns = deps.maxTurns ?? 10;
|
|
6549
|
+
const dispatch = async (skill, branch, backendName, cwd, options) => {
|
|
6550
|
+
const backend = deps.resolveBackend(backendName);
|
|
6551
|
+
if (!backend) {
|
|
6552
|
+
deps.logger.warn(`Maintenance agent dispatch skipped: unknown backend "${backendName}"`, {
|
|
6553
|
+
skill,
|
|
6554
|
+
branch
|
|
6555
|
+
});
|
|
6556
|
+
return { producedCommits: false, fixed: 0 };
|
|
6557
|
+
}
|
|
6558
|
+
const before = headOf(deps.git, cwd);
|
|
6559
|
+
const runner = new AgentRunner(backend, { maxTurns });
|
|
6560
|
+
const session = runner.runSession(
|
|
6561
|
+
syntheticIssue(branch, skill),
|
|
6562
|
+
cwd,
|
|
6563
|
+
buildPrompt(skill, options?.promptContext)
|
|
6564
|
+
);
|
|
6565
|
+
let step = await session.next();
|
|
6566
|
+
while (!step.done) step = await session.next();
|
|
6567
|
+
const after = headOf(deps.git, cwd);
|
|
6568
|
+
let fixed = 0;
|
|
6569
|
+
if (after && after !== before) {
|
|
6570
|
+
if (before) {
|
|
6571
|
+
try {
|
|
6572
|
+
fixed = parseInt(deps.git(["rev-list", "--count", `${before}..${after}`], cwd), 10) || 0;
|
|
6573
|
+
} catch {
|
|
6574
|
+
fixed = 1;
|
|
6575
|
+
}
|
|
6576
|
+
} else {
|
|
6577
|
+
fixed = 1;
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6580
|
+
const producedCommits = fixed > 0;
|
|
6581
|
+
deps.logger.info("Maintenance agent dispatch complete", {
|
|
6582
|
+
skill,
|
|
6583
|
+
branch,
|
|
6584
|
+
backendName,
|
|
6585
|
+
producedCommits,
|
|
6586
|
+
fixed
|
|
6587
|
+
});
|
|
6588
|
+
return { producedCommits, fixed };
|
|
6589
|
+
};
|
|
6590
|
+
return { dispatch };
|
|
6591
|
+
}
|
|
6592
|
+
|
|
6593
|
+
// src/orchestrator.ts
|
|
6594
|
+
import { execFileSync } from "child_process";
|
|
6595
|
+
|
|
6440
6596
|
// src/agent/intelligence-factory.ts
|
|
6441
6597
|
import {
|
|
6442
6598
|
IntelligencePipeline,
|
|
@@ -6947,7 +7103,7 @@ function handleV1InteractionsResolveRoute(req, res, queue) {
|
|
|
6947
7103
|
|
|
6948
7104
|
// src/server/routes/plans.ts
|
|
6949
7105
|
import { z as z5 } from "zod";
|
|
6950
|
-
import * as
|
|
7106
|
+
import * as fs9 from "fs/promises";
|
|
6951
7107
|
import * as path11 from "path";
|
|
6952
7108
|
var PlanWriteSchema = z5.object({
|
|
6953
7109
|
filename: z5.string().min(1),
|
|
@@ -6976,9 +7132,9 @@ function handlePlansRoute(req, res, plansDir) {
|
|
|
6976
7132
|
);
|
|
6977
7133
|
return;
|
|
6978
7134
|
}
|
|
6979
|
-
await
|
|
7135
|
+
await fs9.mkdir(plansDir, { recursive: true });
|
|
6980
7136
|
const filePath = path11.join(plansDir, basename3);
|
|
6981
|
-
await
|
|
7137
|
+
await fs9.writeFile(filePath, parsed.content, "utf-8");
|
|
6982
7138
|
res.writeHead(201, { "Content-Type": "application/json" });
|
|
6983
7139
|
res.end(JSON.stringify({ ok: true, filename: basename3 }));
|
|
6984
7140
|
} catch {
|
|
@@ -7353,11 +7509,10 @@ function handleAnalyzeRoute(req, res, pipeline) {
|
|
|
7353
7509
|
}
|
|
7354
7510
|
|
|
7355
7511
|
// src/server/routes/roadmap-actions.ts
|
|
7356
|
-
import * as fs11 from "fs/promises";
|
|
7357
7512
|
import * as path12 from "path";
|
|
7358
7513
|
import {
|
|
7359
|
-
|
|
7360
|
-
|
|
7514
|
+
resolveRoadmapStoreForFile as resolveRoadmapStoreForFile2,
|
|
7515
|
+
applyRoadmapDiff as applyRoadmapDiff2,
|
|
7361
7516
|
loadProjectRoadmapMode,
|
|
7362
7517
|
loadTrackerClientConfigFromProject,
|
|
7363
7518
|
createTrackerClient,
|
|
@@ -7443,13 +7598,14 @@ function handleRoadmapActionsRoute(req, res, roadmapPath) {
|
|
|
7443
7598
|
sendJSON2(res, 400, { error: "Title must not contain newlines or markdown headings" });
|
|
7444
7599
|
return;
|
|
7445
7600
|
}
|
|
7446
|
-
const
|
|
7447
|
-
const
|
|
7448
|
-
if (!
|
|
7601
|
+
const store = resolveRoadmapStoreForFile2({ roadmapPath });
|
|
7602
|
+
const loaded = await store.load();
|
|
7603
|
+
if (!loaded.ok) {
|
|
7449
7604
|
sendJSON2(res, 500, { error: "Failed to parse roadmap file" });
|
|
7450
7605
|
return;
|
|
7451
7606
|
}
|
|
7452
|
-
const roadmap =
|
|
7607
|
+
const roadmap = loaded.value;
|
|
7608
|
+
const before = structuredClone(roadmap);
|
|
7453
7609
|
let backlog = roadmap.milestones.find((m) => m.isBacklog);
|
|
7454
7610
|
if (!backlog) {
|
|
7455
7611
|
backlog = { name: "Backlog", isBacklog: true, features: [] };
|
|
@@ -7472,10 +7628,11 @@ function handleRoadmapActionsRoute(req, res, roadmapPath) {
|
|
|
7472
7628
|
updatedAt: null
|
|
7473
7629
|
});
|
|
7474
7630
|
roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
7475
|
-
const
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7631
|
+
const persisted = await applyRoadmapDiff2(store, before, roadmap);
|
|
7632
|
+
if (!persisted.ok) {
|
|
7633
|
+
sendJSON2(res, 500, { error: persisted.error.message });
|
|
7634
|
+
return;
|
|
7635
|
+
}
|
|
7479
7636
|
sendJSON2(res, 201, { ok: true, featureName: parsed.title });
|
|
7480
7637
|
} catch (err) {
|
|
7481
7638
|
const msg = err instanceof Error ? err.message : "Failed to append to roadmap";
|
|
@@ -7741,7 +7898,6 @@ function handleV1JobsMaintenanceRoute(req, res, deps) {
|
|
|
7741
7898
|
}
|
|
7742
7899
|
|
|
7743
7900
|
// src/server/routes/v1/events-sse.ts
|
|
7744
|
-
import { randomBytes as randomBytes2 } from "crypto";
|
|
7745
7901
|
var SSE_TOPICS = [
|
|
7746
7902
|
"state_change",
|
|
7747
7903
|
"agent_event",
|
|
@@ -7757,8 +7913,55 @@ var SSE_TOPICS = [
|
|
|
7757
7913
|
"webhook.subscription.deleted"
|
|
7758
7914
|
];
|
|
7759
7915
|
var HEARTBEAT_MS = 15e3;
|
|
7760
|
-
|
|
7761
|
-
|
|
7916
|
+
var DEFAULT_REPLAY_BUFFER = 1024;
|
|
7917
|
+
var SseEventLog = class {
|
|
7918
|
+
seq = 0;
|
|
7919
|
+
buffer = [];
|
|
7920
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
7921
|
+
constructor(bus, cap = DEFAULT_REPLAY_BUFFER) {
|
|
7922
|
+
this.cap = cap;
|
|
7923
|
+
for (const topic of SSE_TOPICS) {
|
|
7924
|
+
bus.on(topic, (data) => this.record(topic, data));
|
|
7925
|
+
}
|
|
7926
|
+
}
|
|
7927
|
+
cap;
|
|
7928
|
+
record(topic, data) {
|
|
7929
|
+
const event = { id: ++this.seq, topic, data };
|
|
7930
|
+
this.buffer.push(event);
|
|
7931
|
+
if (this.buffer.length > this.cap) this.buffer.shift();
|
|
7932
|
+
for (const fn of this.subscribers) fn(event);
|
|
7933
|
+
}
|
|
7934
|
+
/** Highest assigned id (0 when nothing has been recorded yet). */
|
|
7935
|
+
currentSeq() {
|
|
7936
|
+
return this.seq;
|
|
7937
|
+
}
|
|
7938
|
+
/** Buffered events strictly newer than `lastId`, oldest-first. */
|
|
7939
|
+
replayFrom(lastId) {
|
|
7940
|
+
return this.buffer.filter((e) => e.id > lastId);
|
|
7941
|
+
}
|
|
7942
|
+
/** Register a live listener; returns an unsubscribe function. */
|
|
7943
|
+
subscribe(fn) {
|
|
7944
|
+
this.subscribers.add(fn);
|
|
7945
|
+
return () => {
|
|
7946
|
+
this.subscribers.delete(fn);
|
|
7947
|
+
};
|
|
7948
|
+
}
|
|
7949
|
+
};
|
|
7950
|
+
var logsByBus = /* @__PURE__ */ new WeakMap();
|
|
7951
|
+
function getSseEventLog(bus) {
|
|
7952
|
+
let log = logsByBus.get(bus);
|
|
7953
|
+
if (!log) {
|
|
7954
|
+
log = new SseEventLog(bus);
|
|
7955
|
+
logsByBus.set(bus, log);
|
|
7956
|
+
}
|
|
7957
|
+
return log;
|
|
7958
|
+
}
|
|
7959
|
+
function parseLastEventId(req) {
|
|
7960
|
+
const raw = req.headers["last-event-id"];
|
|
7961
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
7962
|
+
if (value === void 0) return null;
|
|
7963
|
+
const n = Number(value);
|
|
7964
|
+
return Number.isInteger(n) && n >= 0 ? n : null;
|
|
7762
7965
|
}
|
|
7763
7966
|
function handleV1EventsSseRoute(req, res, bus) {
|
|
7764
7967
|
if (req.method !== "GET" || req.url !== "/api/v1/events") return false;
|
|
@@ -7770,22 +7973,28 @@ function handleV1EventsSseRoute(req, res, bus) {
|
|
|
7770
7973
|
res.write(`: harness gateway SSE \u2014 connected at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
7771
7974
|
|
|
7772
7975
|
`);
|
|
7773
|
-
const
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
id: ${newEventId()}
|
|
7976
|
+
const log = getSseEventLog(bus);
|
|
7977
|
+
const send = (e) => {
|
|
7978
|
+
try {
|
|
7979
|
+
res.write(`event: ${e.topic}
|
|
7980
|
+
data: ${JSON.stringify(e.data)}
|
|
7981
|
+
id: ${e.id}
|
|
7780
7982
|
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7983
|
+
`);
|
|
7984
|
+
} catch {
|
|
7985
|
+
}
|
|
7986
|
+
};
|
|
7987
|
+
const lastId = parseLastEventId(req);
|
|
7988
|
+
let replayedThrough = lastId ?? log.currentSeq();
|
|
7989
|
+
if (lastId !== null) {
|
|
7990
|
+
for (const e of log.replayFrom(lastId)) {
|
|
7991
|
+
send(e);
|
|
7992
|
+
replayedThrough = e.id;
|
|
7993
|
+
}
|
|
7788
7994
|
}
|
|
7995
|
+
const unsubscribe = log.subscribe((e) => {
|
|
7996
|
+
if (e.id > replayedThrough) send(e);
|
|
7997
|
+
});
|
|
7789
7998
|
const heartbeat = setInterval(() => {
|
|
7790
7999
|
try {
|
|
7791
8000
|
res.write(": heartbeat\n\n");
|
|
@@ -7795,7 +8004,7 @@ id: ${newEventId()}
|
|
|
7795
8004
|
heartbeat.unref();
|
|
7796
8005
|
const cleanup = () => {
|
|
7797
8006
|
clearInterval(heartbeat);
|
|
7798
|
-
|
|
8007
|
+
unsubscribe();
|
|
7799
8008
|
};
|
|
7800
8009
|
res.on("close", cleanup);
|
|
7801
8010
|
res.on("finish", cleanup);
|
|
@@ -8115,7 +8324,7 @@ async function runGate(projectPath, proposalId) {
|
|
|
8115
8324
|
}
|
|
8116
8325
|
|
|
8117
8326
|
// src/proposals/promote.ts
|
|
8118
|
-
import * as
|
|
8327
|
+
import * as fs10 from "fs";
|
|
8119
8328
|
import * as path13 from "path";
|
|
8120
8329
|
import { parse as parseYaml3, stringify as stringifyYaml } from "yaml";
|
|
8121
8330
|
import {
|
|
@@ -8141,7 +8350,7 @@ function skillDir(projectPath, name) {
|
|
|
8141
8350
|
}
|
|
8142
8351
|
function readIfExists(p) {
|
|
8143
8352
|
try {
|
|
8144
|
-
return
|
|
8353
|
+
return fs10.readFileSync(p, "utf-8");
|
|
8145
8354
|
} catch {
|
|
8146
8355
|
return null;
|
|
8147
8356
|
}
|
|
@@ -8187,15 +8396,15 @@ function assertGateReady(proposal) {
|
|
|
8187
8396
|
}
|
|
8188
8397
|
async function promoteNewSkill(projectPath, proposal) {
|
|
8189
8398
|
const target = skillDir(projectPath, proposal.content.name);
|
|
8190
|
-
if (
|
|
8399
|
+
if (fs10.existsSync(target)) {
|
|
8191
8400
|
throw new PromotionError(
|
|
8192
8401
|
`a catalog skill already exists at ${target}; use a refinement proposal to update it`
|
|
8193
8402
|
);
|
|
8194
8403
|
}
|
|
8195
|
-
|
|
8404
|
+
fs10.mkdirSync(target, { recursive: true });
|
|
8196
8405
|
const yamlOut = injectProvenanceIntoYaml(proposal.content.skillYaml ?? "", proposal.id);
|
|
8197
|
-
|
|
8198
|
-
|
|
8406
|
+
fs10.writeFileSync(path13.join(target, "skill.yaml"), yamlOut);
|
|
8407
|
+
fs10.writeFileSync(path13.join(target, "SKILL.md"), proposal.content.skillMd ?? "");
|
|
8199
8408
|
return { skillPath: target };
|
|
8200
8409
|
}
|
|
8201
8410
|
async function promoteRefinement(projectPath, proposal) {
|
|
@@ -8203,7 +8412,7 @@ async function promoteRefinement(projectPath, proposal) {
|
|
|
8203
8412
|
throw new PromotionError("refinement proposal is missing targetSkill");
|
|
8204
8413
|
}
|
|
8205
8414
|
const target = skillDir(projectPath, proposal.targetSkill);
|
|
8206
|
-
if (!
|
|
8415
|
+
if (!fs10.existsSync(target)) {
|
|
8207
8416
|
throw new PromotionError(
|
|
8208
8417
|
`target skill ${proposal.targetSkill} does not exist at ${target}; cannot refine`
|
|
8209
8418
|
);
|
|
@@ -8216,7 +8425,7 @@ async function promoteRefinement(projectPath, proposal) {
|
|
|
8216
8425
|
"no metadata changes detected; check that the reviewer applied the proposed diff before approving"
|
|
8217
8426
|
);
|
|
8218
8427
|
}
|
|
8219
|
-
|
|
8428
|
+
fs10.writeFileSync(yamlPath, after);
|
|
8220
8429
|
return { skillPath: target };
|
|
8221
8430
|
}
|
|
8222
8431
|
async function promote(projectPath, proposalId, decidedBy) {
|
|
@@ -8654,7 +8863,7 @@ function handleV1RoutingRoute(req, res, deps) {
|
|
|
8654
8863
|
}
|
|
8655
8864
|
|
|
8656
8865
|
// src/server/routes/sessions.ts
|
|
8657
|
-
import * as
|
|
8866
|
+
import * as fs11 from "fs/promises";
|
|
8658
8867
|
import * as path14 from "path";
|
|
8659
8868
|
import { z as z15 } from "zod";
|
|
8660
8869
|
var SessionCreateSchema = z15.object({
|
|
@@ -8675,12 +8884,12 @@ function extractSessionId(url) {
|
|
|
8675
8884
|
}
|
|
8676
8885
|
async function handleList2(res, sessionsDir) {
|
|
8677
8886
|
try {
|
|
8678
|
-
const entries = await
|
|
8887
|
+
const entries = await fs11.readdir(sessionsDir, { withFileTypes: true });
|
|
8679
8888
|
const sessions = [];
|
|
8680
8889
|
for (const entry of entries) {
|
|
8681
8890
|
if (!entry.isDirectory()) continue;
|
|
8682
8891
|
try {
|
|
8683
|
-
const content = await
|
|
8892
|
+
const content = await fs11.readFile(
|
|
8684
8893
|
path14.join(sessionsDir, entry.name, "session.json"),
|
|
8685
8894
|
"utf-8"
|
|
8686
8895
|
);
|
|
@@ -8706,7 +8915,7 @@ async function handleGet2(res, id, sessionsDir) {
|
|
|
8706
8915
|
return;
|
|
8707
8916
|
}
|
|
8708
8917
|
try {
|
|
8709
|
-
const content = await
|
|
8918
|
+
const content = await fs11.readFile(path14.join(sessionsDir, id, "session.json"), "utf-8");
|
|
8710
8919
|
jsonResponse(res, 200, JSON.parse(content));
|
|
8711
8920
|
} catch (err) {
|
|
8712
8921
|
if (err.code === "ENOENT") {
|
|
@@ -8730,8 +8939,8 @@ async function handleCreate(req, res, sessionsDir) {
|
|
|
8730
8939
|
return;
|
|
8731
8940
|
}
|
|
8732
8941
|
const sessionDir = path14.join(sessionsDir, session.sessionId);
|
|
8733
|
-
await
|
|
8734
|
-
await
|
|
8942
|
+
await fs11.mkdir(sessionDir, { recursive: true });
|
|
8943
|
+
await fs11.writeFile(path14.join(sessionDir, "session.json"), JSON.stringify(session, null, 2));
|
|
8735
8944
|
jsonResponse(res, 200, { ok: true });
|
|
8736
8945
|
} catch {
|
|
8737
8946
|
jsonResponse(res, 500, { error: "Failed to save session" });
|
|
@@ -8747,8 +8956,8 @@ async function handleUpdate(req, res, url, sessionsDir) {
|
|
|
8747
8956
|
const body = await readBody(req);
|
|
8748
8957
|
const updates = z15.record(z15.unknown()).parse(JSON.parse(body));
|
|
8749
8958
|
const sessionFilePath = path14.join(sessionsDir, id, "session.json");
|
|
8750
|
-
const current = JSON.parse(await
|
|
8751
|
-
await
|
|
8959
|
+
const current = JSON.parse(await fs11.readFile(sessionFilePath, "utf-8"));
|
|
8960
|
+
await fs11.writeFile(sessionFilePath, JSON.stringify({ ...current, ...updates }, null, 2));
|
|
8752
8961
|
jsonResponse(res, 200, { ok: true });
|
|
8753
8962
|
} catch {
|
|
8754
8963
|
jsonResponse(res, 500, { error: "Failed to update session" });
|
|
@@ -8761,7 +8970,7 @@ async function handleDelete(res, url, sessionsDir) {
|
|
|
8761
8970
|
jsonResponse(res, 400, { error: "Missing or invalid sessionId" });
|
|
8762
8971
|
return;
|
|
8763
8972
|
}
|
|
8764
|
-
await
|
|
8973
|
+
await fs11.rm(path14.join(sessionsDir, id), { recursive: true, force: true });
|
|
8765
8974
|
jsonResponse(res, 200, { ok: true });
|
|
8766
8975
|
} catch {
|
|
8767
8976
|
jsonResponse(res, 500, { error: "Failed to delete session" });
|
|
@@ -9009,7 +9218,7 @@ function handleLocalModelsRoute(req, res, getStatuses) {
|
|
|
9009
9218
|
}
|
|
9010
9219
|
|
|
9011
9220
|
// src/server/static.ts
|
|
9012
|
-
import * as
|
|
9221
|
+
import * as fs12 from "fs";
|
|
9013
9222
|
import * as path15 from "path";
|
|
9014
9223
|
var MIME_TYPES = {
|
|
9015
9224
|
".html": "text/html; charset=utf-8",
|
|
@@ -9039,11 +9248,11 @@ function handleStaticFile(req, res, dashboardDir) {
|
|
|
9039
9248
|
if (!resolved.startsWith(path15.resolve(dashboardDir))) {
|
|
9040
9249
|
return serveFile(path15.join(dashboardDir, "index.html"), res);
|
|
9041
9250
|
}
|
|
9042
|
-
if (
|
|
9251
|
+
if (fs12.existsSync(resolved) && fs12.statSync(resolved).isFile()) {
|
|
9043
9252
|
return serveFile(resolved, res);
|
|
9044
9253
|
}
|
|
9045
9254
|
const indexPath = path15.join(dashboardDir, "index.html");
|
|
9046
|
-
if (
|
|
9255
|
+
if (fs12.existsSync(indexPath)) {
|
|
9047
9256
|
return serveFile(indexPath, res);
|
|
9048
9257
|
}
|
|
9049
9258
|
return false;
|
|
@@ -9052,7 +9261,7 @@ function serveFile(filePath, res) {
|
|
|
9052
9261
|
const ext = path15.extname(filePath).toLowerCase();
|
|
9053
9262
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
9054
9263
|
try {
|
|
9055
|
-
const content =
|
|
9264
|
+
const content = fs12.readFileSync(filePath);
|
|
9056
9265
|
res.writeHead(200, { "Content-Type": contentType });
|
|
9057
9266
|
res.end(content);
|
|
9058
9267
|
return true;
|
|
@@ -9062,7 +9271,7 @@ function serveFile(filePath, res) {
|
|
|
9062
9271
|
}
|
|
9063
9272
|
|
|
9064
9273
|
// src/server/plan-watcher.ts
|
|
9065
|
-
import * as
|
|
9274
|
+
import * as fs13 from "fs";
|
|
9066
9275
|
import * as path16 from "path";
|
|
9067
9276
|
var PlanWatcher = class {
|
|
9068
9277
|
plansDir;
|
|
@@ -9077,11 +9286,11 @@ var PlanWatcher = class {
|
|
|
9077
9286
|
* Creates the directory if it does not exist.
|
|
9078
9287
|
*/
|
|
9079
9288
|
start() {
|
|
9080
|
-
|
|
9081
|
-
this.watcher =
|
|
9289
|
+
fs13.mkdirSync(this.plansDir, { recursive: true });
|
|
9290
|
+
this.watcher = fs13.watch(this.plansDir, (eventType, filename) => {
|
|
9082
9291
|
if (eventType === "rename" && filename && filename.endsWith(".md")) {
|
|
9083
9292
|
const filePath = path16.join(this.plansDir, filename);
|
|
9084
|
-
if (
|
|
9293
|
+
if (fs13.existsSync(filePath)) {
|
|
9085
9294
|
void this.handleNewPlan(filename);
|
|
9086
9295
|
}
|
|
9087
9296
|
}
|
|
@@ -9112,8 +9321,8 @@ var PlanWatcher = class {
|
|
|
9112
9321
|
};
|
|
9113
9322
|
|
|
9114
9323
|
// src/auth/tokens.ts
|
|
9115
|
-
import { randomBytes as
|
|
9116
|
-
import { readFile as
|
|
9324
|
+
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
9325
|
+
import { readFile as readFile6, writeFile as writeFile6, mkdir as mkdir7, rename } from "fs/promises";
|
|
9117
9326
|
import { dirname as dirname5 } from "path";
|
|
9118
9327
|
import bcrypt from "bcryptjs";
|
|
9119
9328
|
import {
|
|
@@ -9123,10 +9332,10 @@ import {
|
|
|
9123
9332
|
var BCRYPT_ROUNDS = 12;
|
|
9124
9333
|
var LEGACY_ENV_ID = "tok_legacy_env";
|
|
9125
9334
|
function genId() {
|
|
9126
|
-
return `tok_${
|
|
9335
|
+
return `tok_${randomBytes2(8).toString("hex")}`;
|
|
9127
9336
|
}
|
|
9128
9337
|
function genSecret() {
|
|
9129
|
-
return
|
|
9338
|
+
return randomBytes2(24).toString("base64url");
|
|
9130
9339
|
}
|
|
9131
9340
|
function parseToken(raw) {
|
|
9132
9341
|
const dot = raw.indexOf(".");
|
|
@@ -9142,7 +9351,7 @@ var TokenStore = class {
|
|
|
9142
9351
|
async load() {
|
|
9143
9352
|
if (this.cache) return this.cache;
|
|
9144
9353
|
try {
|
|
9145
|
-
const raw = await
|
|
9354
|
+
const raw = await readFile6(this.path, "utf8");
|
|
9146
9355
|
const parsed = JSON.parse(raw);
|
|
9147
9356
|
const list = Array.isArray(parsed) ? parsed : [];
|
|
9148
9357
|
this.cache = list.map((entry) => {
|
|
@@ -9157,9 +9366,9 @@ var TokenStore = class {
|
|
|
9157
9366
|
}
|
|
9158
9367
|
async persist(records) {
|
|
9159
9368
|
await mkdir7(dirname5(this.path), { recursive: true });
|
|
9160
|
-
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${
|
|
9161
|
-
await
|
|
9162
|
-
await
|
|
9369
|
+
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${randomBytes2(4).toString("hex")}`;
|
|
9370
|
+
await writeFile6(tmp, JSON.stringify(records, null, 2), "utf8");
|
|
9371
|
+
await rename(tmp, this.path);
|
|
9163
9372
|
this.cache = records;
|
|
9164
9373
|
}
|
|
9165
9374
|
async create(input) {
|
|
@@ -9843,8 +10052,8 @@ var OrchestratorServer = class {
|
|
|
9843
10052
|
};
|
|
9844
10053
|
|
|
9845
10054
|
// src/gateway/webhooks/store.ts
|
|
9846
|
-
import { randomBytes as
|
|
9847
|
-
import { readFile as
|
|
10055
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
10056
|
+
import { readFile as readFile7, writeFile as writeFile7, mkdir as mkdir9, rename as rename2, chmod } from "fs/promises";
|
|
9848
10057
|
import { dirname as dirname7 } from "path";
|
|
9849
10058
|
import { WebhookSubscriptionSchema } from "@harness-engineering/types";
|
|
9850
10059
|
|
|
@@ -9870,10 +10079,10 @@ function eventMatches(pattern, type) {
|
|
|
9870
10079
|
|
|
9871
10080
|
// src/gateway/webhooks/store.ts
|
|
9872
10081
|
function genId2() {
|
|
9873
|
-
return `whk_${
|
|
10082
|
+
return `whk_${randomBytes3(8).toString("hex")}`;
|
|
9874
10083
|
}
|
|
9875
10084
|
function genSecret2() {
|
|
9876
|
-
return
|
|
10085
|
+
return randomBytes3(32).toString("base64url");
|
|
9877
10086
|
}
|
|
9878
10087
|
var WebhookStore = class {
|
|
9879
10088
|
constructor(path24) {
|
|
@@ -9884,7 +10093,7 @@ var WebhookStore = class {
|
|
|
9884
10093
|
async load() {
|
|
9885
10094
|
if (this.cache) return this.cache;
|
|
9886
10095
|
try {
|
|
9887
|
-
const raw = await
|
|
10096
|
+
const raw = await readFile7(this.path, "utf8");
|
|
9888
10097
|
const parsed = JSON.parse(raw);
|
|
9889
10098
|
const list = Array.isArray(parsed) ? parsed : [];
|
|
9890
10099
|
this.cache = list.map((entry) => {
|
|
@@ -9898,11 +10107,11 @@ var WebhookStore = class {
|
|
|
9898
10107
|
return this.cache;
|
|
9899
10108
|
}
|
|
9900
10109
|
async persist(records) {
|
|
9901
|
-
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${
|
|
10110
|
+
const tmp = `${this.path}.tmp-${process.pid}-${Date.now()}-${randomBytes3(4).toString("hex")}`;
|
|
9902
10111
|
try {
|
|
9903
10112
|
await mkdir9(dirname7(this.path), { recursive: true });
|
|
9904
|
-
await
|
|
9905
|
-
await
|
|
10113
|
+
await writeFile7(tmp, JSON.stringify(records, null, 2), { encoding: "utf8", mode: 384 });
|
|
10114
|
+
await rename2(tmp, this.path);
|
|
9906
10115
|
await chmod(this.path, 384);
|
|
9907
10116
|
} catch (err) {
|
|
9908
10117
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -9942,7 +10151,7 @@ var WebhookStore = class {
|
|
|
9942
10151
|
};
|
|
9943
10152
|
|
|
9944
10153
|
// src/gateway/webhooks/delivery.ts
|
|
9945
|
-
import { randomBytes as
|
|
10154
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
9946
10155
|
|
|
9947
10156
|
// src/gateway/webhooks/queue.ts
|
|
9948
10157
|
import Database from "better-sqlite3";
|
|
@@ -10152,7 +10361,7 @@ var WebhookDelivery = class {
|
|
|
10152
10361
|
enqueue(sub, event) {
|
|
10153
10362
|
const payload = JSON.stringify(event);
|
|
10154
10363
|
this.queue.insert({
|
|
10155
|
-
id: `dlv_${
|
|
10364
|
+
id: `dlv_${randomBytes4(8).toString("hex")}`,
|
|
10156
10365
|
subscriptionId: sub.id,
|
|
10157
10366
|
eventType: event.type,
|
|
10158
10367
|
payload
|
|
@@ -10260,7 +10469,7 @@ var WebhookDelivery = class {
|
|
|
10260
10469
|
};
|
|
10261
10470
|
|
|
10262
10471
|
// src/gateway/webhooks/events.ts
|
|
10263
|
-
import { randomBytes as
|
|
10472
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
10264
10473
|
var WEBHOOK_TOPICS = [
|
|
10265
10474
|
"interaction.created",
|
|
10266
10475
|
"interaction.resolved",
|
|
@@ -10275,8 +10484,8 @@ var WEBHOOK_TOPICS = [
|
|
|
10275
10484
|
"proposal.approved",
|
|
10276
10485
|
"proposal.rejected"
|
|
10277
10486
|
];
|
|
10278
|
-
function
|
|
10279
|
-
return `evt_${
|
|
10487
|
+
function newEventId() {
|
|
10488
|
+
return `evt_${randomBytes5(8).toString("hex")}`;
|
|
10280
10489
|
}
|
|
10281
10490
|
function wireWebhookFanout({ bus, store, delivery }) {
|
|
10282
10491
|
const handlers = [];
|
|
@@ -10287,7 +10496,7 @@ function wireWebhookFanout({ bus, store, delivery }) {
|
|
|
10287
10496
|
const subs = await store.listForEvent(eventType);
|
|
10288
10497
|
if (subs.length === 0) return;
|
|
10289
10498
|
const event = {
|
|
10290
|
-
id:
|
|
10499
|
+
id: newEventId(),
|
|
10291
10500
|
type: eventType,
|
|
10292
10501
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10293
10502
|
data
|
|
@@ -10306,7 +10515,7 @@ function wireWebhookFanout({ bus, store, delivery }) {
|
|
|
10306
10515
|
}
|
|
10307
10516
|
|
|
10308
10517
|
// src/gateway/telemetry/fanout.ts
|
|
10309
|
-
import { randomBytes as
|
|
10518
|
+
import { randomBytes as randomBytes6 } from "crypto";
|
|
10310
10519
|
import { SpanKind } from "@harness-engineering/core";
|
|
10311
10520
|
var TOPICS = {
|
|
10312
10521
|
MAINTENANCE_STARTED: "maintenance:started",
|
|
@@ -10329,14 +10538,14 @@ var SPAN_NAME = {
|
|
|
10329
10538
|
[TOPICS.DISPATCH_DECISION]: "dispatch_decision",
|
|
10330
10539
|
[TOPICS.SKILL_INVOCATION]: "skill_invocation"
|
|
10331
10540
|
};
|
|
10332
|
-
function
|
|
10333
|
-
return `evt_${
|
|
10541
|
+
function newEventId2() {
|
|
10542
|
+
return `evt_${randomBytes6(8).toString("hex")}`;
|
|
10334
10543
|
}
|
|
10335
10544
|
function newTraceId() {
|
|
10336
|
-
return
|
|
10545
|
+
return randomBytes6(16).toString("hex");
|
|
10337
10546
|
}
|
|
10338
10547
|
function newSpanId() {
|
|
10339
|
-
return
|
|
10548
|
+
return randomBytes6(8).toString("hex");
|
|
10340
10549
|
}
|
|
10341
10550
|
function nowNs() {
|
|
10342
10551
|
return BigInt(Date.now()) * 1000000n;
|
|
@@ -10450,7 +10659,7 @@ function wireTelemetryFanout(params) {
|
|
|
10450
10659
|
};
|
|
10451
10660
|
exporter.push(span);
|
|
10452
10661
|
const gatewayEvent = {
|
|
10453
|
-
id:
|
|
10662
|
+
id: newEventId2(),
|
|
10454
10663
|
type: TELEMETRY_TYPE[topic],
|
|
10455
10664
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10456
10665
|
data: payload,
|
|
@@ -10644,7 +10853,7 @@ function buildSlackSink(config, options) {
|
|
|
10644
10853
|
}
|
|
10645
10854
|
|
|
10646
10855
|
// src/notifications/events.ts
|
|
10647
|
-
import { randomBytes as
|
|
10856
|
+
import { randomBytes as randomBytes7 } from "crypto";
|
|
10648
10857
|
|
|
10649
10858
|
// src/notifications/envelope.ts
|
|
10650
10859
|
function asObj(data) {
|
|
@@ -10775,8 +10984,8 @@ var NOTIFICATION_TOPICS = [
|
|
|
10775
10984
|
"proposal.approved",
|
|
10776
10985
|
"proposal.rejected"
|
|
10777
10986
|
];
|
|
10778
|
-
function
|
|
10779
|
-
return `evt_${
|
|
10987
|
+
function newEventId3() {
|
|
10988
|
+
return `evt_${randomBytes7(8).toString("hex")}`;
|
|
10780
10989
|
}
|
|
10781
10990
|
function dispatchToEntry(bus, entry, event) {
|
|
10782
10991
|
const eventType = event.type;
|
|
@@ -10811,7 +11020,7 @@ function wireNotificationSinks({ bus, registry }) {
|
|
|
10811
11020
|
const entries = registry.list();
|
|
10812
11021
|
if (entries.length === 0) return;
|
|
10813
11022
|
const event = {
|
|
10814
|
-
id:
|
|
11023
|
+
id: newEventId3(),
|
|
10815
11024
|
type: eventType,
|
|
10816
11025
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10817
11026
|
data
|
|
@@ -10922,6 +11131,37 @@ async function scanWorkspaceConfig(workspacePath) {
|
|
|
10922
11131
|
return { exitCode: computeScanExitCode(results), results };
|
|
10923
11132
|
}
|
|
10924
11133
|
|
|
11134
|
+
// src/core/lane-persistence.ts
|
|
11135
|
+
import { eventSourcing } from "@harness-engineering/core";
|
|
11136
|
+
import { Err as Err21 } from "@harness-engineering/types";
|
|
11137
|
+
var SIGNAL_TO_LANE = {
|
|
11138
|
+
claim: "claimed",
|
|
11139
|
+
dispatch: "in_progress",
|
|
11140
|
+
success: "in_review",
|
|
11141
|
+
failure: "blocked",
|
|
11142
|
+
abandon: "canceled"
|
|
11143
|
+
};
|
|
11144
|
+
function mapOrchestratorLane(signal) {
|
|
11145
|
+
return SIGNAL_TO_LANE[signal];
|
|
11146
|
+
}
|
|
11147
|
+
async function persistLane(projectPath, issueId, signal) {
|
|
11148
|
+
try {
|
|
11149
|
+
const reg = await eventSourcing.registerTask(projectPath, issueId, []);
|
|
11150
|
+
if (!reg.ok) return reg;
|
|
11151
|
+
return await eventSourcing.transitionLane(projectPath, issueId, mapOrchestratorLane(signal));
|
|
11152
|
+
} catch (err) {
|
|
11153
|
+
return Err21(err instanceof Error ? err : new Error(String(err)));
|
|
11154
|
+
}
|
|
11155
|
+
}
|
|
11156
|
+
async function readPersistedLanes(projectPath) {
|
|
11157
|
+
try {
|
|
11158
|
+
const snap = await eventSourcing.readSnapshot(projectPath);
|
|
11159
|
+
return snap.ok ? snap.value.lanes : { tasks: {} };
|
|
11160
|
+
} catch {
|
|
11161
|
+
return { tasks: {} };
|
|
11162
|
+
}
|
|
11163
|
+
}
|
|
11164
|
+
|
|
10925
11165
|
// src/maintenance/task-registry.ts
|
|
10926
11166
|
var BUILT_IN_TASKS = [
|
|
10927
11167
|
// --- Mechanical-AI ---
|
|
@@ -10985,7 +11225,13 @@ var BUILT_IN_TASKS = [
|
|
|
10985
11225
|
description: "Detect and fix cross-check violations",
|
|
10986
11226
|
schedule: "0 6 * * 1",
|
|
10987
11227
|
branch: "harness-maint/cross-check-fixes",
|
|
10988
|
-
|
|
11228
|
+
// `harness cross-check` is a dedicated read-only CLI subcommand that surfaces
|
|
11229
|
+
// JUST cross-artifact consistency (plan→implementation coverage + staleness),
|
|
11230
|
+
// mirroring the `validate_cross_check` MCP tool's core (`runCrossCheck`)
|
|
11231
|
+
// WITHOUT running the full `harness validate` suite. It prints a parseable
|
|
11232
|
+
// `Cross-check: N issues` line and exits 0 (clean) / 1 (N issues), so the
|
|
11233
|
+
// maintenance runner reports real results instead of an honest `failure`.
|
|
11234
|
+
checkCommand: ["cross-check"],
|
|
10989
11235
|
fixSkill: "harness-cross-check-fix"
|
|
10990
11236
|
},
|
|
10991
11237
|
// --- Pure-AI ---
|
|
@@ -11044,7 +11290,10 @@ var BUILT_IN_TASKS = [
|
|
|
11044
11290
|
description: "Assess overall project health",
|
|
11045
11291
|
schedule: "0 6 * * *",
|
|
11046
11292
|
branch: null,
|
|
11047
|
-
|
|
11293
|
+
// `assess_project` is an MCP tool name, not a CLI subcommand. The CLI
|
|
11294
|
+
// composite project-health report is `harness insights` (health, entropy,
|
|
11295
|
+
// decay, attention, impact) — a read-only report that records metrics.
|
|
11296
|
+
checkCommand: ["insights"]
|
|
11048
11297
|
},
|
|
11049
11298
|
{
|
|
11050
11299
|
id: "stale-constraints",
|
|
@@ -11052,7 +11301,14 @@ var BUILT_IN_TASKS = [
|
|
|
11052
11301
|
description: "Detect stale architectural constraints",
|
|
11053
11302
|
schedule: "0 2 1 * *",
|
|
11054
11303
|
branch: null,
|
|
11055
|
-
|
|
11304
|
+
// `harness stale-constraints` is a dedicated read-only CLI subcommand that
|
|
11305
|
+
// surfaces the `detect_stale_constraints` MCP tool's core in-process. It is
|
|
11306
|
+
// precondition-gated on the knowledge graph: with no graph it emits the
|
|
11307
|
+
// "No knowledge graph found. Run `harness scan` first." signature and exits
|
|
11308
|
+
// non-zero, which the runner classifies as `skipped` (not failure). With a
|
|
11309
|
+
// graph it prints a parseable `Stale constraints: N findings` line and exits
|
|
11310
|
+
// 0 (clean) / 1 (N stale), recorded as report-only metrics.
|
|
11311
|
+
checkCommand: ["stale-constraints"]
|
|
11056
11312
|
},
|
|
11057
11313
|
{
|
|
11058
11314
|
id: "graph-refresh",
|
|
@@ -11085,7 +11341,8 @@ var BUILT_IN_TASKS = [
|
|
|
11085
11341
|
description: "Clean up stale orchestrator sessions",
|
|
11086
11342
|
schedule: "0 0 * * *",
|
|
11087
11343
|
branch: null,
|
|
11088
|
-
checkCommand: ["cleanup-sessions"]
|
|
11344
|
+
checkCommand: ["cleanup-sessions"],
|
|
11345
|
+
excludeFromHumanSweep: true
|
|
11089
11346
|
},
|
|
11090
11347
|
{
|
|
11091
11348
|
id: "perf-baselines",
|
|
@@ -11093,7 +11350,8 @@ var BUILT_IN_TASKS = [
|
|
|
11093
11350
|
description: "Update performance baselines",
|
|
11094
11351
|
schedule: "0 7 * * *",
|
|
11095
11352
|
branch: null,
|
|
11096
|
-
checkCommand: ["perf", "baselines", "update"]
|
|
11353
|
+
checkCommand: ["perf", "baselines", "update"],
|
|
11354
|
+
excludeFromHumanSweep: true
|
|
11097
11355
|
},
|
|
11098
11356
|
{
|
|
11099
11357
|
id: "main-sync",
|
|
@@ -11101,7 +11359,8 @@ var BUILT_IN_TASKS = [
|
|
|
11101
11359
|
description: "Fast-forward local default branch from origin",
|
|
11102
11360
|
schedule: "*/15 * * * *",
|
|
11103
11361
|
branch: null,
|
|
11104
|
-
checkCommand: ["harness", "sync-main", "--json"]
|
|
11362
|
+
checkCommand: ["harness", "sync-main", "--json"],
|
|
11363
|
+
excludeFromHumanSweep: true
|
|
11105
11364
|
},
|
|
11106
11365
|
// Hermes Phase 4 — one-shot backfill that stamps `provenance: user-authored`
|
|
11107
11366
|
// on every existing catalog skill. Schedule is Feb 31 (a date that never
|
|
@@ -11114,7 +11373,8 @@ var BUILT_IN_TASKS = [
|
|
|
11114
11373
|
description: "Backfill provenance: user-authored on every existing skill (one-shot, idempotent)",
|
|
11115
11374
|
schedule: "0 0 31 2 *",
|
|
11116
11375
|
branch: null,
|
|
11117
|
-
checkCommand: ["backfill-skill-provenance"]
|
|
11376
|
+
checkCommand: ["backfill-skill-provenance"],
|
|
11377
|
+
excludeFromHumanSweep: true
|
|
11118
11378
|
}
|
|
11119
11379
|
];
|
|
11120
11380
|
|
|
@@ -11181,6 +11441,17 @@ function cronMatchesNow(expression, now) {
|
|
|
11181
11441
|
const daysOfWeek = parseField(dowField, 0, 6);
|
|
11182
11442
|
return minutes.has(minute) && hours.has(hour) && daysOfMonth.has(dayOfMonth) && months.has(month) && daysOfWeek.has(dayOfWeek);
|
|
11183
11443
|
}
|
|
11444
|
+
function cronMatchesDate(expression, date) {
|
|
11445
|
+
const fields = expression.trim().split(/\s+/);
|
|
11446
|
+
if (fields.length !== 5) {
|
|
11447
|
+
throw new Error(`Invalid cron expression: expected 5 fields, got ${fields.length}`);
|
|
11448
|
+
}
|
|
11449
|
+
const [, , domField, monthField, dowField] = fields;
|
|
11450
|
+
const daysOfMonth = parseField(domField, 1, 31);
|
|
11451
|
+
const months = parseField(monthField, 1, 12);
|
|
11452
|
+
const daysOfWeek = parseField(dowField, 0, 6);
|
|
11453
|
+
return daysOfMonth.has(date.getDate()) && months.has(date.getMonth() + 1) && daysOfWeek.has(date.getDay());
|
|
11454
|
+
}
|
|
11184
11455
|
|
|
11185
11456
|
// src/maintenance/scheduler.ts
|
|
11186
11457
|
var MaintenanceScheduler = class {
|
|
@@ -11430,7 +11701,7 @@ var SingleProcessLeaderElector = class {
|
|
|
11430
11701
|
};
|
|
11431
11702
|
|
|
11432
11703
|
// src/maintenance/reporter.ts
|
|
11433
|
-
import * as
|
|
11704
|
+
import * as fs14 from "fs";
|
|
11434
11705
|
import * as path18 from "path";
|
|
11435
11706
|
import { z as z17 } from "zod";
|
|
11436
11707
|
var RunResultSchema = z17.object({
|
|
@@ -11466,9 +11737,9 @@ var MaintenanceReporter = class {
|
|
|
11466
11737
|
*/
|
|
11467
11738
|
async load() {
|
|
11468
11739
|
try {
|
|
11469
|
-
await
|
|
11740
|
+
await fs14.promises.mkdir(this.persistDir, { recursive: true });
|
|
11470
11741
|
const filePath = path18.join(this.persistDir, "history.json");
|
|
11471
|
-
const data = await
|
|
11742
|
+
const data = await fs14.promises.readFile(filePath, "utf-8");
|
|
11472
11743
|
const parsed = z17.array(RunResultSchema).safeParse(JSON.parse(data));
|
|
11473
11744
|
if (parsed.success) {
|
|
11474
11745
|
this.history = parsed.data.slice(0, MAX_HISTORY);
|
|
@@ -11502,9 +11773,9 @@ var MaintenanceReporter = class {
|
|
|
11502
11773
|
*/
|
|
11503
11774
|
async persist() {
|
|
11504
11775
|
try {
|
|
11505
|
-
await
|
|
11776
|
+
await fs14.promises.mkdir(this.persistDir, { recursive: true });
|
|
11506
11777
|
const filePath = path18.join(this.persistDir, "history.json");
|
|
11507
|
-
await
|
|
11778
|
+
await fs14.promises.writeFile(filePath, JSON.stringify(this.history, null, 2), "utf-8");
|
|
11508
11779
|
} catch (err) {
|
|
11509
11780
|
this.logger.error("MaintenanceReporter: failed to persist history", { error: String(err) });
|
|
11510
11781
|
}
|
|
@@ -11544,20 +11815,20 @@ var TaskRunner = class {
|
|
|
11544
11815
|
* @param origin - Hermes Phase 2 trigger-source tag; defaults to `'cron'`
|
|
11545
11816
|
* when called from the scheduler path.
|
|
11546
11817
|
*/
|
|
11547
|
-
async run(task, origin = "cron") {
|
|
11818
|
+
async run(task, origin = "cron", mode = "fix") {
|
|
11548
11819
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11549
11820
|
let result;
|
|
11550
11821
|
let captured;
|
|
11551
11822
|
try {
|
|
11552
11823
|
switch (task.type) {
|
|
11553
11824
|
case "mechanical-ai": {
|
|
11554
|
-
const out = await this.runMechanicalAI(task, startedAt);
|
|
11825
|
+
const out = await this.runMechanicalAI(task, startedAt, mode);
|
|
11555
11826
|
result = out.result;
|
|
11556
11827
|
captured = out.captured;
|
|
11557
11828
|
break;
|
|
11558
11829
|
}
|
|
11559
11830
|
case "pure-ai":
|
|
11560
|
-
result = await this.runPureAI(task, startedAt);
|
|
11831
|
+
result = await this.runPureAI(task, startedAt, mode);
|
|
11561
11832
|
break;
|
|
11562
11833
|
case "report-only": {
|
|
11563
11834
|
const out = await this.runReportOnly(task, startedAt);
|
|
@@ -11566,7 +11837,7 @@ var TaskRunner = class {
|
|
|
11566
11837
|
break;
|
|
11567
11838
|
}
|
|
11568
11839
|
case "housekeeping": {
|
|
11569
|
-
const out = await this.runHousekeeping(task, startedAt);
|
|
11840
|
+
const out = await this.runHousekeeping(task, startedAt, mode);
|
|
11570
11841
|
result = out.result;
|
|
11571
11842
|
captured = out.captured;
|
|
11572
11843
|
break;
|
|
@@ -11630,7 +11901,8 @@ var TaskRunner = class {
|
|
|
11630
11901
|
findings: r2.findings,
|
|
11631
11902
|
stdout: r2.output,
|
|
11632
11903
|
stderr: r2.stderr,
|
|
11633
|
-
structured: r2.structured ? r2.structured : null
|
|
11904
|
+
structured: r2.structured ? r2.structured : null,
|
|
11905
|
+
executionFailed: false
|
|
11634
11906
|
};
|
|
11635
11907
|
}
|
|
11636
11908
|
if (!task.checkCommand || task.checkCommand.length === 0) {
|
|
@@ -11642,7 +11914,8 @@ var TaskRunner = class {
|
|
|
11642
11914
|
findings: r.findings,
|
|
11643
11915
|
stdout: r.output,
|
|
11644
11916
|
stderr: "",
|
|
11645
|
-
structured: null
|
|
11917
|
+
structured: null,
|
|
11918
|
+
executionFailed: r.executionFailed ?? false
|
|
11646
11919
|
};
|
|
11647
11920
|
}
|
|
11648
11921
|
/**
|
|
@@ -11667,7 +11940,7 @@ var TaskRunner = class {
|
|
|
11667
11940
|
* only if fixable findings exist; persist captured stdout/stderr/context
|
|
11668
11941
|
* via the output store on the way out.
|
|
11669
11942
|
*/
|
|
11670
|
-
async runMechanicalAI(task, startedAt) {
|
|
11943
|
+
async runMechanicalAI(task, startedAt, mode = "fix") {
|
|
11671
11944
|
if (!task.fixSkill) {
|
|
11672
11945
|
return wrap(this.failureResult(task.id, startedAt, "mechanical-ai task missing fixSkill"));
|
|
11673
11946
|
}
|
|
@@ -11689,6 +11962,27 @@ var TaskRunner = class {
|
|
|
11689
11962
|
} catch (err) {
|
|
11690
11963
|
return wrap(this.failureResult(task.id, startedAt, String(err)));
|
|
11691
11964
|
}
|
|
11965
|
+
if (check.executionFailed) {
|
|
11966
|
+
const cls = classifyCheckExecutionFailure(`${check.stdout}
|
|
11967
|
+
${check.stderr}`);
|
|
11968
|
+
if (cls.kind === "unrunnable") {
|
|
11969
|
+
return wrap(
|
|
11970
|
+
this.failureResult(task.id, startedAt, checkExecutionError(check)),
|
|
11971
|
+
captureFromCheck(check)
|
|
11972
|
+
);
|
|
11973
|
+
}
|
|
11974
|
+
if (cls.kind === "precondition") {
|
|
11975
|
+
return wrap(
|
|
11976
|
+
this.skippedResult(task.id, startedAt, cls.reason ?? "precondition not met"),
|
|
11977
|
+
captureFromCheck(check)
|
|
11978
|
+
);
|
|
11979
|
+
}
|
|
11980
|
+
check = {
|
|
11981
|
+
...check,
|
|
11982
|
+
executionFailed: false,
|
|
11983
|
+
findings: check.findings > 0 ? check.findings : recoverFindingsCount(check.stdout)
|
|
11984
|
+
};
|
|
11985
|
+
}
|
|
11692
11986
|
const promptContext = await this.composePromptContext(task);
|
|
11693
11987
|
const baseCaptured = {
|
|
11694
11988
|
stdout: check.stdout,
|
|
@@ -11697,13 +11991,14 @@ var TaskRunner = class {
|
|
|
11697
11991
|
...promptContext ? { context: promptContext } : {}
|
|
11698
11992
|
};
|
|
11699
11993
|
const wakeAgentExplicitlyFalse = check.structured !== null && typeof check.structured === "object" && check.structured.wakeAgent === false;
|
|
11700
|
-
if (check.findings === 0 || wakeAgentExplicitlyFalse) {
|
|
11994
|
+
if (check.findings === 0 || wakeAgentExplicitlyFalse || mode === "report") {
|
|
11995
|
+
const reportedWithFindings = mode === "report" && check.findings > 0;
|
|
11701
11996
|
return {
|
|
11702
11997
|
result: {
|
|
11703
11998
|
taskId: task.id,
|
|
11704
11999
|
startedAt,
|
|
11705
12000
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11706
|
-
status: "no-issues",
|
|
12001
|
+
status: reportedWithFindings ? "success" : "no-issues",
|
|
11707
12002
|
findings: check.findings,
|
|
11708
12003
|
fixed: 0,
|
|
11709
12004
|
prUrl: null,
|
|
@@ -11773,7 +12068,19 @@ var TaskRunner = class {
|
|
|
11773
12068
|
/**
|
|
11774
12069
|
* Pure-AI: always dispatch agent with configured skill.
|
|
11775
12070
|
*/
|
|
11776
|
-
async runPureAI(task, startedAt) {
|
|
12071
|
+
async runPureAI(task, startedAt, mode = "fix") {
|
|
12072
|
+
if (mode === "report") {
|
|
12073
|
+
return {
|
|
12074
|
+
taskId: task.id,
|
|
12075
|
+
startedAt,
|
|
12076
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12077
|
+
status: "no-issues",
|
|
12078
|
+
findings: 0,
|
|
12079
|
+
fixed: 0,
|
|
12080
|
+
prUrl: null,
|
|
12081
|
+
prUpdated: false
|
|
12082
|
+
};
|
|
12083
|
+
}
|
|
11777
12084
|
if (!task.fixSkill) {
|
|
11778
12085
|
return this.failureResult(task.id, startedAt, "pure-ai task missing fixSkill");
|
|
11779
12086
|
}
|
|
@@ -11847,6 +12154,27 @@ var TaskRunner = class {
|
|
|
11847
12154
|
return wrap(this.failureResult(task.id, startedAt, String(err)));
|
|
11848
12155
|
}
|
|
11849
12156
|
const parsed = parseStatusLine(check.stdout);
|
|
12157
|
+
if (parsed === null && check.executionFailed) {
|
|
12158
|
+
const cls = classifyCheckExecutionFailure(`${check.stdout}
|
|
12159
|
+
${check.stderr}`);
|
|
12160
|
+
if (cls.kind === "unrunnable") {
|
|
12161
|
+
return wrap(
|
|
12162
|
+
this.failureResult(task.id, startedAt, checkExecutionError(check)),
|
|
12163
|
+
captureFromCheck(check)
|
|
12164
|
+
);
|
|
12165
|
+
}
|
|
12166
|
+
if (cls.kind === "precondition") {
|
|
12167
|
+
return wrap(
|
|
12168
|
+
this.skippedResult(task.id, startedAt, cls.reason ?? "precondition not met"),
|
|
12169
|
+
captureFromCheck(check)
|
|
12170
|
+
);
|
|
12171
|
+
}
|
|
12172
|
+
check = {
|
|
12173
|
+
...check,
|
|
12174
|
+
executionFailed: false,
|
|
12175
|
+
findings: check.findings > 0 ? check.findings : recoverFindingsCount(check.stdout)
|
|
12176
|
+
};
|
|
12177
|
+
}
|
|
11850
12178
|
const status = parsed?.status ?? "success";
|
|
11851
12179
|
const findings = parsed === null ? check.findings : typeof parsed.candidatesFound === "number" ? parsed.candidatesFound : 0;
|
|
11852
12180
|
const result = {
|
|
@@ -11880,7 +12208,20 @@ var TaskRunner = class {
|
|
|
11880
12208
|
* Hermes Phase 2: a `checkScript` may replace `checkCommand` for housekeeping
|
|
11881
12209
|
* tasks; the runner falls through to the same JSON-status parsing path.
|
|
11882
12210
|
*/
|
|
11883
|
-
async runHousekeeping(task, startedAt) {
|
|
12211
|
+
async runHousekeeping(task, startedAt, mode = "fix") {
|
|
12212
|
+
if (mode === "report") {
|
|
12213
|
+
return wrap({
|
|
12214
|
+
taskId: task.id,
|
|
12215
|
+
startedAt,
|
|
12216
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12217
|
+
status: "skipped",
|
|
12218
|
+
findings: 0,
|
|
12219
|
+
fixed: 0,
|
|
12220
|
+
prUrl: null,
|
|
12221
|
+
prUpdated: false,
|
|
12222
|
+
error: "skipped in report mode: housekeeping may mutate and report runs are read-only"
|
|
12223
|
+
});
|
|
12224
|
+
}
|
|
11884
12225
|
if (!task.checkCommand && !task.checkScript) {
|
|
11885
12226
|
return wrap(
|
|
11886
12227
|
this.failureResult(
|
|
@@ -11947,10 +12288,86 @@ var TaskRunner = class {
|
|
|
11947
12288
|
error
|
|
11948
12289
|
};
|
|
11949
12290
|
}
|
|
12291
|
+
/**
|
|
12292
|
+
* A precondition-gated check (e.g. `predict` with <3 snapshots, or a
|
|
12293
|
+
* graph-backed check before `harness scan`). The command is correctly
|
|
12294
|
+
* configured; the repo just lacks the state it needs. Reported as `skipped`
|
|
12295
|
+
* — distinct from a hard `failure` — carrying the refusal line as the reason
|
|
12296
|
+
* (surfaced in the run-report summary column). ADR 0050.
|
|
12297
|
+
*/
|
|
12298
|
+
skippedResult(taskId, startedAt, reason) {
|
|
12299
|
+
return {
|
|
12300
|
+
taskId,
|
|
12301
|
+
startedAt,
|
|
12302
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12303
|
+
status: "skipped",
|
|
12304
|
+
findings: 0,
|
|
12305
|
+
fixed: 0,
|
|
12306
|
+
prUrl: null,
|
|
12307
|
+
prUpdated: false,
|
|
12308
|
+
error: reason
|
|
12309
|
+
};
|
|
12310
|
+
}
|
|
11950
12311
|
};
|
|
11951
12312
|
function wrap(result, captured) {
|
|
11952
12313
|
return captured ? { result, captured } : { result };
|
|
11953
12314
|
}
|
|
12315
|
+
function checkExecutionError(check) {
|
|
12316
|
+
const detail = (check.stderr || check.stdout || "").trim();
|
|
12317
|
+
return detail ? `check command failed to execute: ${detail}` : "check command failed to execute";
|
|
12318
|
+
}
|
|
12319
|
+
function captureFromCheck(check) {
|
|
12320
|
+
return { stdout: check.stdout, stderr: check.stderr, structured: check.structured };
|
|
12321
|
+
}
|
|
12322
|
+
var PRECONDITION_PATTERNS = [
|
|
12323
|
+
/requires at least \d+ snapshots?/i,
|
|
12324
|
+
/Run ["'`]?harness snapshot/i,
|
|
12325
|
+
/no knowledge graph found/i,
|
|
12326
|
+
/Run ["'`]?harness scan/i,
|
|
12327
|
+
/no graph (?:available|found)/i
|
|
12328
|
+
];
|
|
12329
|
+
var UNRUNNABLE_PATTERNS = [
|
|
12330
|
+
/unknown command/i,
|
|
12331
|
+
/unknown option/i,
|
|
12332
|
+
/\bENOENT\b/,
|
|
12333
|
+
/command not found/i,
|
|
12334
|
+
/cannot find module/i,
|
|
12335
|
+
// A timed-out check did not complete — it is a hard failure, never a
|
|
12336
|
+
// "ran-no-count" success. The runners synthesize this message on SIGTERM.
|
|
12337
|
+
/timed out/i,
|
|
12338
|
+
/\bETIMEDOUT\b/
|
|
12339
|
+
];
|
|
12340
|
+
var TIMEOUT_SIGNATURE = /check timed out after \d+\s*ms/i;
|
|
12341
|
+
function explicitFindingsCount(output) {
|
|
12342
|
+
const after = output.match(/(?:findings?|issues?|violations?|errors?)\s*[:=]\s*(\d+)/i);
|
|
12343
|
+
if (after) return parseInt(after[1], 10);
|
|
12344
|
+
const before = output.match(/(\d+)\s+(?:findings?|issues?|violations?|errors?)\b/i);
|
|
12345
|
+
if (before) return parseInt(before[1], 10);
|
|
12346
|
+
return null;
|
|
12347
|
+
}
|
|
12348
|
+
function recoverFindingsCount(output) {
|
|
12349
|
+
return explicitFindingsCount(output) ?? 1;
|
|
12350
|
+
}
|
|
12351
|
+
function firstMeaningfulLine(output) {
|
|
12352
|
+
const line = output.split("\n").map((l) => l.trim()).find(Boolean);
|
|
12353
|
+
if (!line) return "precondition not met";
|
|
12354
|
+
return line.replace(/^[x✗✓!-]\s*/u, "").trim();
|
|
12355
|
+
}
|
|
12356
|
+
var CLASSIFY_HEAD_LINES = 3;
|
|
12357
|
+
function classifyCheckExecutionFailure(output) {
|
|
12358
|
+
const text = (output ?? "").trim();
|
|
12359
|
+
if (text.length === 0) return { kind: "unrunnable" };
|
|
12360
|
+
if (TIMEOUT_SIGNATURE.test(text)) return { kind: "unrunnable" };
|
|
12361
|
+
if (explicitFindingsCount(text) !== null) return { kind: "ran-no-count" };
|
|
12362
|
+
const head = text.split("\n").filter((l) => l.trim()).slice(0, CLASSIFY_HEAD_LINES).join("\n");
|
|
12363
|
+
for (const re of PRECONDITION_PATTERNS) {
|
|
12364
|
+
if (re.test(head)) return { kind: "precondition", reason: firstMeaningfulLine(text) };
|
|
12365
|
+
}
|
|
12366
|
+
for (const re of UNRUNNABLE_PATTERNS) {
|
|
12367
|
+
if (re.test(head)) return { kind: "unrunnable" };
|
|
12368
|
+
}
|
|
12369
|
+
return { kind: "ran-no-count" };
|
|
12370
|
+
}
|
|
11954
12371
|
function parseStatusLine(output) {
|
|
11955
12372
|
const lines = output.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
11956
12373
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
@@ -12099,8 +12516,49 @@ function heuristicResult(stdout, stderr, exitedAbnormally) {
|
|
|
12099
12516
|
};
|
|
12100
12517
|
}
|
|
12101
12518
|
|
|
12519
|
+
// src/maintenance/check-runner.ts
|
|
12520
|
+
import { execFile as nodeExecFile } from "child_process";
|
|
12521
|
+
import { promisify as promisify4 } from "util";
|
|
12522
|
+
var MAINTENANCE_CHECK_MAX_BUFFER = 64 * 1024 * 1024;
|
|
12523
|
+
var MAINTENANCE_CHECK_TIMEOUT_MS = 3e5;
|
|
12524
|
+
var FINDINGS_RE = /(\d+)\s+(?:finding|issue|violation|error)/i;
|
|
12525
|
+
var nodeExecFileAsync = promisify4(nodeExecFile);
|
|
12526
|
+
function isCheckTimeoutError(e) {
|
|
12527
|
+
return e.killed === true || e.signal === "SIGTERM" || e.code === "ETIMEDOUT";
|
|
12528
|
+
}
|
|
12529
|
+
async function runHarnessCheck(spawn7, cwd, opts = {}) {
|
|
12530
|
+
const execFileAsync2 = opts.execFileAsync ?? nodeExecFileAsync;
|
|
12531
|
+
const timeoutMs = opts.timeoutMs ?? MAINTENANCE_CHECK_TIMEOUT_MS;
|
|
12532
|
+
const maxBuffer = opts.maxBuffer ?? MAINTENANCE_CHECK_MAX_BUFFER;
|
|
12533
|
+
try {
|
|
12534
|
+
const { stdout } = await execFileAsync2(spawn7.file, spawn7.args, {
|
|
12535
|
+
cwd,
|
|
12536
|
+
timeout: timeoutMs,
|
|
12537
|
+
maxBuffer
|
|
12538
|
+
});
|
|
12539
|
+
const text = String(stdout);
|
|
12540
|
+
const m = text.match(FINDINGS_RE);
|
|
12541
|
+
const findings = m ? parseInt(m[1], 10) : 0;
|
|
12542
|
+
return { passed: findings === 0, findings, output: text, executionFailed: false };
|
|
12543
|
+
} catch (err) {
|
|
12544
|
+
const e = err;
|
|
12545
|
+
let output = [e.stdout, e.stderr].map((v) => v == null ? "" : String(v)).filter(Boolean).join("\n");
|
|
12546
|
+
if (isCheckTimeoutError(e)) {
|
|
12547
|
+
const note = `check timed out after ${timeoutMs}ms`;
|
|
12548
|
+
output = output.trim() ? `${output}
|
|
12549
|
+
${note}` : note;
|
|
12550
|
+
return { passed: false, findings: 0, output, executionFailed: true };
|
|
12551
|
+
}
|
|
12552
|
+
const m = output.match(FINDINGS_RE);
|
|
12553
|
+
if (m) {
|
|
12554
|
+
return { passed: false, findings: parseInt(m[1], 10), output, executionFailed: false };
|
|
12555
|
+
}
|
|
12556
|
+
return { passed: false, findings: 0, output, executionFailed: true };
|
|
12557
|
+
}
|
|
12558
|
+
}
|
|
12559
|
+
|
|
12102
12560
|
// src/maintenance/output-store.ts
|
|
12103
|
-
import * as
|
|
12561
|
+
import * as fs15 from "fs";
|
|
12104
12562
|
import * as path20 from "path";
|
|
12105
12563
|
var DEFAULT_RETENTION = {
|
|
12106
12564
|
runs: 50,
|
|
@@ -12141,13 +12599,13 @@ var TaskOutputStore = class {
|
|
|
12141
12599
|
async write(taskId, entry, retention) {
|
|
12142
12600
|
this.ensureSafeTaskId(taskId);
|
|
12143
12601
|
const dir = this.dirFor(taskId);
|
|
12144
|
-
await
|
|
12602
|
+
await fs15.promises.mkdir(dir, { recursive: true });
|
|
12145
12603
|
const fileName = `${sanitizeIso(entry.completedAt || (/* @__PURE__ */ new Date()).toISOString())}.json`;
|
|
12146
12604
|
const filePath = path20.join(dir, fileName);
|
|
12147
12605
|
const tmpPath = `${filePath}.tmp`;
|
|
12148
12606
|
const payload = JSON.stringify(entry, null, 2);
|
|
12149
|
-
await
|
|
12150
|
-
await
|
|
12607
|
+
await fs15.promises.writeFile(tmpPath, payload, "utf-8");
|
|
12608
|
+
await fs15.promises.rename(tmpPath, filePath);
|
|
12151
12609
|
try {
|
|
12152
12610
|
await this.applyRetention(taskId, retention);
|
|
12153
12611
|
} catch (err) {
|
|
@@ -12198,7 +12656,7 @@ var TaskOutputStore = class {
|
|
|
12198
12656
|
}
|
|
12199
12657
|
async readEntry(filePath) {
|
|
12200
12658
|
try {
|
|
12201
|
-
const buf = await
|
|
12659
|
+
const buf = await fs15.promises.readFile(filePath, "utf-8");
|
|
12202
12660
|
const parsed = JSON.parse(buf);
|
|
12203
12661
|
return parsed;
|
|
12204
12662
|
} catch {
|
|
@@ -12220,7 +12678,7 @@ var TaskOutputStore = class {
|
|
|
12220
12678
|
const toRemove = /* @__PURE__ */ new Set([...overflow, ...aged]);
|
|
12221
12679
|
for (const name of toRemove) {
|
|
12222
12680
|
try {
|
|
12223
|
-
await
|
|
12681
|
+
await fs15.promises.unlink(path20.join(dir, name));
|
|
12224
12682
|
} catch {
|
|
12225
12683
|
}
|
|
12226
12684
|
}
|
|
@@ -12229,7 +12687,7 @@ var TaskOutputStore = class {
|
|
|
12229
12687
|
async function listJsonFilesDescending(dir) {
|
|
12230
12688
|
let names;
|
|
12231
12689
|
try {
|
|
12232
|
-
names = await
|
|
12690
|
+
names = await fs15.promises.readdir(dir);
|
|
12233
12691
|
} catch {
|
|
12234
12692
|
return [];
|
|
12235
12693
|
}
|
|
@@ -12338,7 +12796,7 @@ var fallbackLogger3 = {
|
|
|
12338
12796
|
};
|
|
12339
12797
|
|
|
12340
12798
|
// src/maintenance/custom-task-validator.ts
|
|
12341
|
-
import { Ok as Ok23, Err as
|
|
12799
|
+
import { Ok as Ok23, Err as Err22 } from "@harness-engineering/types";
|
|
12342
12800
|
var ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
12343
12801
|
var REQUIRED_FIELDS_BY_TYPE = {
|
|
12344
12802
|
"mechanical-ai": ["branch", "fixSkill"],
|
|
@@ -12358,7 +12816,7 @@ function validateCustomTasks(customTasks, builtIns, deps = {}) {
|
|
|
12358
12816
|
validateOne(id, task, builtInIds, allIds, deps, errors);
|
|
12359
12817
|
}
|
|
12360
12818
|
detectCycles(customTasks, builtIns, errors);
|
|
12361
|
-
return errors.length === 0 ? Ok23(void 0) :
|
|
12819
|
+
return errors.length === 0 ? Ok23(void 0) : Err22(errors);
|
|
12362
12820
|
}
|
|
12363
12821
|
function validateOne(id, task, builtInIds, allIds, deps, errors) {
|
|
12364
12822
|
const prefix = `customTasks.${id}`;
|
|
@@ -12547,6 +13005,11 @@ function deriveSeedPaths(config) {
|
|
|
12547
13005
|
const roadmapPath = config.tracker.kind === "roadmap" && config.tracker.filePath ? config.tracker.filePath : "docs/roadmap.md";
|
|
12548
13006
|
return [".harness/proposals", roadmapPath];
|
|
12549
13007
|
}
|
|
13008
|
+
function normalizeHarnessCommand(command) {
|
|
13009
|
+
if (command.length === 0) return [];
|
|
13010
|
+
if (command[0] === "harness") return command;
|
|
13011
|
+
return ["harness", ...command];
|
|
13012
|
+
}
|
|
12550
13013
|
var Orchestrator = class extends EventEmitter {
|
|
12551
13014
|
state;
|
|
12552
13015
|
config;
|
|
@@ -12673,6 +13136,11 @@ var Orchestrator = class extends EventEmitter {
|
|
|
12673
13136
|
abortControllers = /* @__PURE__ */ new Map();
|
|
12674
13137
|
/** Guards against overlapping ticks when a tick takes longer than the polling interval */
|
|
12675
13138
|
tickInProgress = false;
|
|
13139
|
+
/** Phase 4 (DLane-5): lanes read back from the durable log on first tick, for
|
|
13140
|
+
* observability only — NOT fed into reconciliation. Empty until the first tick. */
|
|
13141
|
+
persistedLanes = { tasks: {} };
|
|
13142
|
+
/** Ensures the lane read-back diagnostic runs at most once (first tick). */
|
|
13143
|
+
laneReadbackDone = false;
|
|
12676
13144
|
/** Timestamp of the last stale branch sweep (at most once per hour) */
|
|
12677
13145
|
lastBranchSweepMs = 0;
|
|
12678
13146
|
/** Current tick-phase activity visible to the dashboard */
|
|
@@ -12919,48 +13387,30 @@ var Orchestrator = class extends EventEmitter {
|
|
|
12919
13387
|
const logger = this.logger;
|
|
12920
13388
|
const checkRunner = {
|
|
12921
13389
|
run: async (command, cwd) => {
|
|
12922
|
-
const
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
const [cmd, ...args] = command;
|
|
12926
|
-
if (!cmd) return { passed: true, findings: 0, output: "" };
|
|
12927
|
-
try {
|
|
12928
|
-
const { stdout } = await execFileAsync2(cmd, args, { cwd, timeout: 12e4 });
|
|
12929
|
-
const findingsMatch = stdout.match(/(\d+)\s+(?:finding|issue|violation|error)/i);
|
|
12930
|
-
const findings = findingsMatch ? parseInt(findingsMatch[1], 10) : 0;
|
|
12931
|
-
return { passed: findings === 0, findings, output: stdout };
|
|
12932
|
-
} catch (err) {
|
|
12933
|
-
const error = err;
|
|
12934
|
-
const output = [error.stdout, error.stderr].filter(Boolean).join("\n");
|
|
12935
|
-
const findingsMatch = output.match(/(\d+)\s+(?:finding|issue|violation|error)/i);
|
|
12936
|
-
const findings = findingsMatch ? parseInt(findingsMatch[1], 10) : 1;
|
|
12937
|
-
return { passed: false, findings, output };
|
|
12938
|
-
}
|
|
12939
|
-
}
|
|
12940
|
-
};
|
|
12941
|
-
const agentDispatcher = {
|
|
12942
|
-
dispatch: async (skill, branch, backendName, cwd) => {
|
|
12943
|
-
logger.info(
|
|
12944
|
-
"Maintenance agent dispatcher invoked (stub \u2014 skill dispatch integration pending)",
|
|
12945
|
-
{
|
|
12946
|
-
skill,
|
|
12947
|
-
branch,
|
|
12948
|
-
backendName,
|
|
12949
|
-
cwd
|
|
12950
|
-
}
|
|
12951
|
-
);
|
|
12952
|
-
return { producedCommits: false, fixed: 0 };
|
|
13390
|
+
const [cmd, ...args] = normalizeHarnessCommand(command);
|
|
13391
|
+
if (!cmd) return { passed: true, findings: 0, output: "", executionFailed: false };
|
|
13392
|
+
return runHarnessCheck({ file: cmd, args }, cwd);
|
|
12953
13393
|
}
|
|
12954
13394
|
};
|
|
13395
|
+
const resolveBackend2 = makeBackendResolver(this.getBackends());
|
|
13396
|
+
const agentDispatcher = createAgentDispatcher({
|
|
13397
|
+
resolveBackend: resolveBackend2,
|
|
13398
|
+
git: (args, cwd) => execFileSync("git", args, { cwd, encoding: "utf-8" }).toString().trim(),
|
|
13399
|
+
logger
|
|
13400
|
+
});
|
|
12955
13401
|
const commandExecutor = {
|
|
12956
13402
|
exec: async (command, cwd) => {
|
|
12957
13403
|
const { execFile: execFile7 } = await import("child_process");
|
|
12958
|
-
const { promisify:
|
|
12959
|
-
const execFileAsync2 =
|
|
12960
|
-
const [cmd, ...args] = command;
|
|
13404
|
+
const { promisify: promisify6 } = await import("util");
|
|
13405
|
+
const execFileAsync2 = promisify6(execFile7);
|
|
13406
|
+
const [cmd, ...args] = normalizeHarnessCommand(command);
|
|
12961
13407
|
if (!cmd) return { stdout: "" };
|
|
12962
13408
|
try {
|
|
12963
|
-
const { stdout } = await execFileAsync2(cmd, args, {
|
|
13409
|
+
const { stdout } = await execFileAsync2(cmd, args, {
|
|
13410
|
+
cwd,
|
|
13411
|
+
timeout: MAINTENANCE_CHECK_TIMEOUT_MS,
|
|
13412
|
+
maxBuffer: MAINTENANCE_CHECK_MAX_BUFFER
|
|
13413
|
+
});
|
|
12964
13414
|
return { stdout: String(stdout) };
|
|
12965
13415
|
} catch (err) {
|
|
12966
13416
|
logger.warn("Maintenance command execution failed", {
|
|
@@ -13095,6 +13545,10 @@ ${messages}`);
|
|
|
13095
13545
|
async asyncTick() {
|
|
13096
13546
|
await this.ensureClaimManager();
|
|
13097
13547
|
await this.intelligenceRunner.loadPersistedData();
|
|
13548
|
+
if (!this.laneReadbackDone) {
|
|
13549
|
+
this.laneReadbackDone = true;
|
|
13550
|
+
await this.readBackPersistedLanes();
|
|
13551
|
+
}
|
|
13098
13552
|
const nowMs = Date.now();
|
|
13099
13553
|
this.setTickActivity("fetching", "Polling tracker for candidates");
|
|
13100
13554
|
const candidatesResult = await this.tracker.fetchCandidateIssues();
|
|
@@ -13247,12 +13701,48 @@ ${messages}`);
|
|
|
13247
13701
|
break;
|
|
13248
13702
|
case "escalate":
|
|
13249
13703
|
await this.handleEscalation(effect);
|
|
13704
|
+
await this.persistLaneSafe(effect.issueId, "abandon");
|
|
13250
13705
|
break;
|
|
13251
13706
|
case "claim":
|
|
13252
13707
|
await this.handleClaimEffect(effect);
|
|
13253
13708
|
break;
|
|
13254
13709
|
}
|
|
13255
13710
|
}
|
|
13711
|
+
/**
|
|
13712
|
+
* Phase 4 (DLane-5): persist an orchestrator lane transition to the durable
|
|
13713
|
+
* core event log at the effect boundary. NEVER throws — `persistLane` returns
|
|
13714
|
+
* an `Err` Result on failure, which is logged and swallowed here so a
|
|
13715
|
+
* lane-persistence failure can never break orchestrator dispatch.
|
|
13716
|
+
*/
|
|
13717
|
+
async persistLaneSafe(issueId, signal) {
|
|
13718
|
+
const r = await persistLane(this.projectRoot, issueId, signal);
|
|
13719
|
+
if (!r.ok) {
|
|
13720
|
+
this.logger.warn(`lane persist failed for ${issueId} (${signal}): ${r.error.message}`);
|
|
13721
|
+
}
|
|
13722
|
+
}
|
|
13723
|
+
/**
|
|
13724
|
+
* Phase 4 (DLane-5): read persisted task lanes back from the durable log and
|
|
13725
|
+
* log a one-line summary. Stores the projection on `this.persistedLanes` for
|
|
13726
|
+
* observability. Read-only — never feeds reconciliation, never throws.
|
|
13727
|
+
*/
|
|
13728
|
+
async readBackPersistedLanes() {
|
|
13729
|
+
const lanes = await readPersistedLanes(this.projectRoot);
|
|
13730
|
+
this.persistedLanes = lanes;
|
|
13731
|
+
const entries = Object.keys(lanes.tasks).map((id) => `${id}:${lanes.tasks[id]?.lane}`);
|
|
13732
|
+
const nonTerminal = entries.filter((e) => !e.endsWith(":done") && !e.endsWith(":canceled"));
|
|
13733
|
+
this.logger.info(
|
|
13734
|
+
`Lane read-back on startup: ${entries.length} persisted task(s), ${nonTerminal.length} non-terminal`,
|
|
13735
|
+
{ nonTerminal }
|
|
13736
|
+
);
|
|
13737
|
+
}
|
|
13738
|
+
/**
|
|
13739
|
+
* Phase 4 (DLane-5): the task lanes most recently read back from the durable
|
|
13740
|
+
* log on startup, exposed for external observability. Read-only — a fresh
|
|
13741
|
+
* `{ tasks: {} }` until the first tick's read-back has run.
|
|
13742
|
+
*/
|
|
13743
|
+
getPersistedLanes() {
|
|
13744
|
+
return this.persistedLanes;
|
|
13745
|
+
}
|
|
13256
13746
|
/**
|
|
13257
13747
|
* Guards workspace cleanup by checking whether the agent pushed a branch
|
|
13258
13748
|
* that does not yet have a pull request. If so, the worktree is preserved
|
|
@@ -13439,6 +13929,7 @@ ${messages}`);
|
|
|
13439
13929
|
}
|
|
13440
13930
|
return;
|
|
13441
13931
|
}
|
|
13932
|
+
await this.persistLaneSafe(effect.issue.id, "claim");
|
|
13442
13933
|
await this.postClaimComment(effect.issue);
|
|
13443
13934
|
await this.dispatchIssue(effect.issue, effect.attempt, effect.backend);
|
|
13444
13935
|
}
|
|
@@ -13499,6 +13990,7 @@ ${messages}`);
|
|
|
13499
13990
|
this.logger.info(`Dispatching issue: ${issue.identifier} (attempt ${attempt})`, {
|
|
13500
13991
|
issueId: issue.id
|
|
13501
13992
|
});
|
|
13993
|
+
await this.persistLaneSafe(issue.id, "dispatch");
|
|
13502
13994
|
try {
|
|
13503
13995
|
const workspaceResult = await this.workspace.ensureWorkspace(issue.identifier);
|
|
13504
13996
|
if (!workspaceResult.ok) throw workspaceResult.error;
|
|
@@ -13707,6 +14199,7 @@ ${messages}`);
|
|
|
13707
14199
|
* Informs the state machine that an agent worker has exited.
|
|
13708
14200
|
*/
|
|
13709
14201
|
async emitWorkerExit(issueId, reason, attempt, error) {
|
|
14202
|
+
await this.persistLaneSafe(issueId, reason === "normal" ? "success" : "failure");
|
|
13710
14203
|
await this.completionHandler.handleWorkerExit(
|
|
13711
14204
|
issueId,
|
|
13712
14205
|
reason,
|
|
@@ -14242,11 +14735,11 @@ function launchTUI(orchestrator) {
|
|
|
14242
14735
|
}
|
|
14243
14736
|
|
|
14244
14737
|
// src/maintenance/sync-main.ts
|
|
14245
|
-
import { execFile as
|
|
14246
|
-
import { promisify as
|
|
14738
|
+
import { execFile as nodeExecFile2 } from "child_process";
|
|
14739
|
+
import { promisify as promisify5 } from "util";
|
|
14247
14740
|
var DEFAULT_TIMEOUT_MS3 = 6e4;
|
|
14248
14741
|
async function git(execFileFn, args, cwd, timeoutMs) {
|
|
14249
|
-
const exec =
|
|
14742
|
+
const exec = promisify5(execFileFn);
|
|
14250
14743
|
const { stdout, stderr } = await exec("git", args, { cwd, timeout: timeoutMs });
|
|
14251
14744
|
return { stdout: String(stdout), stderr: String(stderr) };
|
|
14252
14745
|
}
|
|
@@ -14308,7 +14801,7 @@ async function isAncestor(execFileFn, a, b, cwd, timeoutMs) {
|
|
|
14308
14801
|
}
|
|
14309
14802
|
}
|
|
14310
14803
|
async function syncMain(repoRoot, opts = {}) {
|
|
14311
|
-
const execFileFn = opts.execFileFn ??
|
|
14804
|
+
const execFileFn = opts.execFileFn ?? nodeExecFile2;
|
|
14312
14805
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
|
|
14313
14806
|
try {
|
|
14314
14807
|
const originRef = await resolveOriginDefault(execFileFn, repoRoot, timeoutMs);
|
|
@@ -14385,8 +14878,56 @@ async function syncMain(repoRoot, opts = {}) {
|
|
|
14385
14878
|
}
|
|
14386
14879
|
}
|
|
14387
14880
|
|
|
14881
|
+
// src/maintenance/overdue.ts
|
|
14882
|
+
var LOOKBACK_DAYS = 366;
|
|
14883
|
+
function previousFireTime(schedule, now) {
|
|
14884
|
+
let day = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
14885
|
+
for (let d = 0; d <= LOOKBACK_DAYS; d++) {
|
|
14886
|
+
if (cronMatchesDate(schedule, day)) {
|
|
14887
|
+
const dayStartMs = day.getTime();
|
|
14888
|
+
const scanStart = d === 0 ? new Date(
|
|
14889
|
+
now.getFullYear(),
|
|
14890
|
+
now.getMonth(),
|
|
14891
|
+
now.getDate(),
|
|
14892
|
+
now.getHours(),
|
|
14893
|
+
now.getMinutes()
|
|
14894
|
+
) : new Date(day.getFullYear(), day.getMonth(), day.getDate(), 23, 59);
|
|
14895
|
+
for (let ms = scanStart.getTime(); ms >= dayStartMs; ms -= 6e4) {
|
|
14896
|
+
const candidate = new Date(ms);
|
|
14897
|
+
if (cronMatchesNow(schedule, candidate)) return candidate;
|
|
14898
|
+
}
|
|
14899
|
+
}
|
|
14900
|
+
day = new Date(day.getFullYear(), day.getMonth(), day.getDate() - 1);
|
|
14901
|
+
}
|
|
14902
|
+
return null;
|
|
14903
|
+
}
|
|
14904
|
+
function isSatisfyingRun(r) {
|
|
14905
|
+
return r.status === "success" || r.status === "no-issues";
|
|
14906
|
+
}
|
|
14907
|
+
function isOverdue(task, history, now) {
|
|
14908
|
+
const satisfyingRuns = history.filter((r) => r.taskId === task.id && isSatisfyingRun(r));
|
|
14909
|
+
const fire = previousFireTime(task.schedule, now);
|
|
14910
|
+
if (fire === null) return satisfyingRuns.length === 0;
|
|
14911
|
+
const fireMs = fire.getTime();
|
|
14912
|
+
const satisfied = satisfyingRuns.some((r) => new Date(r.completedAt).getTime() >= fireMs);
|
|
14913
|
+
return !satisfied;
|
|
14914
|
+
}
|
|
14915
|
+
function selectTasks(tasks, history, filter) {
|
|
14916
|
+
const eligible = tasks.filter((t) => t.excludeFromHumanSweep !== true);
|
|
14917
|
+
switch (filter.mode) {
|
|
14918
|
+
case "all":
|
|
14919
|
+
return eligible;
|
|
14920
|
+
case "ids": {
|
|
14921
|
+
const wanted = new Set(filter.ids ?? []);
|
|
14922
|
+
return eligible.filter((t) => wanted.has(t.id));
|
|
14923
|
+
}
|
|
14924
|
+
case "overdue":
|
|
14925
|
+
return eligible.filter((t) => isOverdue(t, history, filter.now));
|
|
14926
|
+
}
|
|
14927
|
+
}
|
|
14928
|
+
|
|
14388
14929
|
// src/sessions/search-index.ts
|
|
14389
|
-
import * as
|
|
14930
|
+
import * as fs16 from "fs";
|
|
14390
14931
|
import * as path22 from "path";
|
|
14391
14932
|
import Database2 from "better-sqlite3";
|
|
14392
14933
|
import { INDEXED_FILE_KINDS } from "@harness-engineering/types";
|
|
@@ -14447,7 +14988,7 @@ var SqliteSearchIndex = class {
|
|
|
14447
14988
|
removeSessionStmt;
|
|
14448
14989
|
totalStmt;
|
|
14449
14990
|
constructor(dbPath) {
|
|
14450
|
-
|
|
14991
|
+
fs16.mkdirSync(path22.dirname(dbPath), { recursive: true });
|
|
14451
14992
|
this.db = new Database2(dbPath);
|
|
14452
14993
|
this.db.pragma("journal_mode = WAL");
|
|
14453
14994
|
this.db.pragma("synchronous = NORMAL");
|
|
@@ -14553,12 +15094,12 @@ function indexSessionDirectory(idx, args) {
|
|
|
14553
15094
|
for (const kind of kinds) {
|
|
14554
15095
|
const fileName = FILE_KIND_TO_FILENAME[kind];
|
|
14555
15096
|
const filePath = path22.join(args.sessionDir, fileName);
|
|
14556
|
-
if (!
|
|
14557
|
-
let body =
|
|
15097
|
+
if (!fs16.existsSync(filePath)) continue;
|
|
15098
|
+
let body = fs16.readFileSync(filePath, "utf8");
|
|
14558
15099
|
if (Buffer.byteLength(body, "utf8") > cap) {
|
|
14559
15100
|
body = body.slice(0, cap) + "\n\n[TRUNCATED]";
|
|
14560
15101
|
}
|
|
14561
|
-
const stat =
|
|
15102
|
+
const stat = fs16.statSync(filePath);
|
|
14562
15103
|
const relPath = path22.relative(args.projectPath, filePath).replaceAll("\\", "/");
|
|
14563
15104
|
idx.upsertSessionDoc({
|
|
14564
15105
|
sessionId: args.sessionId,
|
|
@@ -14580,8 +15121,8 @@ function reindexFromArchive(projectPath, opts = {}) {
|
|
|
14580
15121
|
idx.resetArchived();
|
|
14581
15122
|
let sessionsIndexed = 0;
|
|
14582
15123
|
let docsWritten = 0;
|
|
14583
|
-
if (
|
|
14584
|
-
const entries =
|
|
15124
|
+
if (fs16.existsSync(archiveBase)) {
|
|
15125
|
+
const entries = fs16.readdirSync(archiveBase, { withFileTypes: true });
|
|
14585
15126
|
for (const entry of entries) {
|
|
14586
15127
|
if (!entry.isDirectory()) continue;
|
|
14587
15128
|
const sessionDir = path22.join(archiveBase, entry.name);
|
|
@@ -14604,12 +15145,12 @@ function reindexFromArchive(projectPath, opts = {}) {
|
|
|
14604
15145
|
}
|
|
14605
15146
|
|
|
14606
15147
|
// src/sessions/summarize.ts
|
|
14607
|
-
import * as
|
|
15148
|
+
import * as fs17 from "fs";
|
|
14608
15149
|
import * as path23 from "path";
|
|
14609
15150
|
import {
|
|
14610
15151
|
SessionSummarySchema
|
|
14611
15152
|
} from "@harness-engineering/types";
|
|
14612
|
-
import { Ok as Ok24, Err as
|
|
15153
|
+
import { Ok as Ok24, Err as Err23 } from "@harness-engineering/types";
|
|
14613
15154
|
var LLM_SUMMARY_FILE = "llm-summary.md";
|
|
14614
15155
|
var SUMMARY_INPUT_FILES = [
|
|
14615
15156
|
{ filename: "summary.md", kind: "summary" },
|
|
@@ -14636,9 +15177,9 @@ function readInputCorpus(archiveDir) {
|
|
|
14636
15177
|
const parts = [];
|
|
14637
15178
|
for (const { filename, kind } of SUMMARY_INPUT_FILES) {
|
|
14638
15179
|
const p = path23.join(archiveDir, filename);
|
|
14639
|
-
if (!
|
|
15180
|
+
if (!fs17.existsSync(p)) continue;
|
|
14640
15181
|
try {
|
|
14641
|
-
const content =
|
|
15182
|
+
const content = fs17.readFileSync(p, "utf8");
|
|
14642
15183
|
if (content.trim().length === 0) continue;
|
|
14643
15184
|
parts.push(`## FILE: ${kind}
|
|
14644
15185
|
|
|
@@ -14700,17 +15241,17 @@ status: failed
|
|
|
14700
15241
|
|
|
14701
15242
|
- reason: ${reason}
|
|
14702
15243
|
`;
|
|
14703
|
-
|
|
15244
|
+
fs17.writeFileSync(filePath, body, "utf8");
|
|
14704
15245
|
return filePath;
|
|
14705
15246
|
}
|
|
14706
15247
|
async function summarizeArchivedSession(ctx) {
|
|
14707
15248
|
const writeStubOnError = ctx.writeStubOnError ?? true;
|
|
14708
|
-
if (!
|
|
14709
|
-
return
|
|
15249
|
+
if (!fs17.existsSync(ctx.archiveDir)) {
|
|
15250
|
+
return Err23(new Error(`archive directory not found: ${ctx.archiveDir}`));
|
|
14710
15251
|
}
|
|
14711
15252
|
const corpus = readInputCorpus(ctx.archiveDir);
|
|
14712
15253
|
if (corpus.trim().length === 0) {
|
|
14713
|
-
return
|
|
15254
|
+
return Err23(new Error(`no summary input files found in ${ctx.archiveDir}`));
|
|
14714
15255
|
}
|
|
14715
15256
|
const inputBudgetTokens = ctx.config?.inputBudgetTokens ?? DEFAULT_INPUT_BUDGET_TOKENS;
|
|
14716
15257
|
const truncated = truncateForBudget(corpus, inputBudgetTokens);
|
|
@@ -14743,7 +15284,7 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14743
15284
|
} catch {
|
|
14744
15285
|
}
|
|
14745
15286
|
}
|
|
14746
|
-
return
|
|
15287
|
+
return Err23(
|
|
14747
15288
|
new Error(`session summary failed: ${reason}` + (stubPath ? ` (stub: ${stubPath})` : ""))
|
|
14748
15289
|
);
|
|
14749
15290
|
}
|
|
@@ -14757,7 +15298,7 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14757
15298
|
} catch {
|
|
14758
15299
|
}
|
|
14759
15300
|
}
|
|
14760
|
-
return
|
|
15301
|
+
return Err23(new Error(reason));
|
|
14761
15302
|
}
|
|
14762
15303
|
const meta = {
|
|
14763
15304
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -14768,7 +15309,7 @@ async function summarizeArchivedSession(ctx) {
|
|
|
14768
15309
|
};
|
|
14769
15310
|
const filePath = path23.join(ctx.archiveDir, LLM_SUMMARY_FILE);
|
|
14770
15311
|
const body = renderLlmSummaryMarkdown(parsed.data, meta);
|
|
14771
|
-
|
|
15312
|
+
fs17.writeFileSync(filePath, body, "utf8");
|
|
14772
15313
|
return Ok24({ summary: parsed.data, meta, filePath });
|
|
14773
15314
|
}
|
|
14774
15315
|
function isSummaryEnabled(config) {
|
|
@@ -14848,13 +15389,18 @@ export {
|
|
|
14848
15389
|
BUILT_IN_TASKS,
|
|
14849
15390
|
BackendDefSchema,
|
|
14850
15391
|
BackendRouter,
|
|
15392
|
+
CheckScriptRunner,
|
|
14851
15393
|
ClaimManager,
|
|
14852
15394
|
GateNotReadyError,
|
|
14853
15395
|
GateRunError,
|
|
14854
15396
|
InteractionQueue,
|
|
15397
|
+
LinearGraphQLClient,
|
|
14855
15398
|
LinearGraphQLStub,
|
|
14856
15399
|
LocalModelResolver,
|
|
15400
|
+
MAINTENANCE_CHECK_MAX_BUFFER,
|
|
15401
|
+
MAINTENANCE_CHECK_TIMEOUT_MS,
|
|
14857
15402
|
MAX_ATTEMPTS,
|
|
15403
|
+
MaintenanceReporter,
|
|
14858
15404
|
MockBackend,
|
|
14859
15405
|
ORCHESTRATOR_IDENTITY_FILE,
|
|
14860
15406
|
Orchestrator,
|
|
@@ -14872,6 +15418,7 @@ export {
|
|
|
14872
15418
|
SqliteSearchIndex,
|
|
14873
15419
|
StreamRecorder,
|
|
14874
15420
|
TaskOutputStore,
|
|
15421
|
+
TaskRunner,
|
|
14875
15422
|
TokenStore,
|
|
14876
15423
|
WebhookQueue,
|
|
14877
15424
|
WorkflowLoader,
|
|
@@ -14882,7 +15429,9 @@ export {
|
|
|
14882
15429
|
buildArchiveHooks,
|
|
14883
15430
|
calculateRetryDelay,
|
|
14884
15431
|
canDispatch,
|
|
15432
|
+
classifyCheckExecutionFailure,
|
|
14885
15433
|
computeRateLimitDelay,
|
|
15434
|
+
createAgentDispatcher,
|
|
14886
15435
|
createBackend,
|
|
14887
15436
|
createEmptyState,
|
|
14888
15437
|
crossFieldRoutingIssues,
|
|
@@ -14894,22 +15443,27 @@ export {
|
|
|
14894
15443
|
emitProposalApproved,
|
|
14895
15444
|
emitProposalCreated,
|
|
14896
15445
|
emitProposalRejected,
|
|
15446
|
+
explicitFindingsCount,
|
|
14897
15447
|
extractHighlights,
|
|
14898
15448
|
extractTitlePrefix,
|
|
14899
15449
|
getAvailableSlots,
|
|
14900
15450
|
getDefaultConfig,
|
|
14901
15451
|
getPerStateCount,
|
|
14902
15452
|
indexSessionDirectory,
|
|
15453
|
+
isCheckTimeoutError,
|
|
14903
15454
|
isEligible,
|
|
14904
15455
|
isSummaryEnabled,
|
|
14905
15456
|
launchTUI,
|
|
14906
15457
|
loadPublishedIndex,
|
|
15458
|
+
makeBackendResolver,
|
|
14907
15459
|
migrateAgentConfig,
|
|
14908
15460
|
normalizeFts5Query,
|
|
15461
|
+
normalizeHarnessCommand,
|
|
14909
15462
|
normalizeLocalModel,
|
|
14910
15463
|
openSearchIndex,
|
|
14911
15464
|
promote,
|
|
14912
15465
|
reconcile,
|
|
15466
|
+
recoverFindingsCount,
|
|
14913
15467
|
reindexFromArchive,
|
|
14914
15468
|
renderAnalysisComment,
|
|
14915
15469
|
renderLlmSummaryMarkdown,
|
|
@@ -14919,9 +15473,11 @@ export {
|
|
|
14919
15473
|
routeIssue,
|
|
14920
15474
|
routingWarnings,
|
|
14921
15475
|
runGate,
|
|
15476
|
+
runHarnessCheck,
|
|
14922
15477
|
savePublishedIndex,
|
|
14923
15478
|
searchIndexPath,
|
|
14924
15479
|
selectCandidates,
|
|
15480
|
+
selectTasks,
|
|
14925
15481
|
sortCandidates,
|
|
14926
15482
|
summarizeArchivedSession,
|
|
14927
15483
|
syncMain,
|