@harness-engineering/orchestrator 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +398 -287
- package/dist/index.mjs +398 -287
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -2445,6 +2445,9 @@ var RoadmapTrackerAdapter = class {
|
|
|
2445
2445
|
// src/tracker/extensions/linear.ts
|
|
2446
2446
|
import { Ok as Ok5, Err as Err4 } from "@harness-engineering/types";
|
|
2447
2447
|
var LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
|
|
2448
|
+
function errorMessage(err) {
|
|
2449
|
+
return err instanceof Error ? err.message : String(err);
|
|
2450
|
+
}
|
|
2448
2451
|
var LinearGraphQLClient = class {
|
|
2449
2452
|
apiKey;
|
|
2450
2453
|
endpoint;
|
|
@@ -2455,9 +2458,23 @@ var LinearGraphQLClient = class {
|
|
|
2455
2458
|
this.fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
2456
2459
|
}
|
|
2457
2460
|
async query(query, variables) {
|
|
2458
|
-
|
|
2461
|
+
const sent = await this.sendRequest(query, variables);
|
|
2462
|
+
if (!sent.ok) return sent;
|
|
2463
|
+
const res = sent.value;
|
|
2464
|
+
if (!res.ok) {
|
|
2465
|
+
return Err4(await this.httpError(res));
|
|
2466
|
+
}
|
|
2467
|
+
const parsed = await this.parseEnvelope(res);
|
|
2468
|
+
if (!parsed.ok) return parsed;
|
|
2469
|
+
const envelope = parsed.value;
|
|
2470
|
+
const graphqlError = envelopeError(envelope);
|
|
2471
|
+
if (graphqlError) return Err4(graphqlError);
|
|
2472
|
+
return Ok5(envelope.data ?? {});
|
|
2473
|
+
}
|
|
2474
|
+
/** POST the operation to Linear, normalizing transport throws into an `Err`. */
|
|
2475
|
+
async sendRequest(query, variables) {
|
|
2459
2476
|
try {
|
|
2460
|
-
res = await this.fetchFn(this.endpoint, {
|
|
2477
|
+
const res = await this.fetchFn(this.endpoint, {
|
|
2461
2478
|
method: "POST",
|
|
2462
2479
|
headers: {
|
|
2463
2480
|
Authorization: this.apiKey,
|
|
@@ -2465,35 +2482,31 @@ var LinearGraphQLClient = class {
|
|
|
2465
2482
|
},
|
|
2466
2483
|
body: JSON.stringify({ query, variables: variables ?? {} })
|
|
2467
2484
|
});
|
|
2485
|
+
return Ok5(res);
|
|
2468
2486
|
} 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}`));
|
|
2487
|
+
return Err4(new Error(`Linear GraphQL request failed: ${errorMessage(err)}`));
|
|
2479
2488
|
}
|
|
2480
|
-
|
|
2489
|
+
}
|
|
2490
|
+
/** Build the error for a non-2xx HTTP response, including a truncated body. */
|
|
2491
|
+
async httpError(res) {
|
|
2492
|
+
const body = await res.text().catch(() => "");
|
|
2493
|
+
const detail = body ? `: ${body.slice(0, 500)}` : "";
|
|
2494
|
+
return new Error(`Linear GraphQL HTTP ${res.status}${detail}`);
|
|
2495
|
+
}
|
|
2496
|
+
/** Parse the JSON envelope, normalizing parse failures into an `Err`. */
|
|
2497
|
+
async parseEnvelope(res) {
|
|
2481
2498
|
try {
|
|
2482
|
-
|
|
2499
|
+
return Ok5(await res.json());
|
|
2483
2500
|
} 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}`));
|
|
2501
|
+
return Err4(new Error(`Linear GraphQL response was not valid JSON: ${errorMessage(err)}`));
|
|
2493
2502
|
}
|
|
2494
|
-
return Ok5(envelope.data ?? {});
|
|
2495
2503
|
}
|
|
2496
2504
|
};
|
|
2505
|
+
function envelopeError(envelope) {
|
|
2506
|
+
if (!envelope.errors || envelope.errors.length === 0) return void 0;
|
|
2507
|
+
const message = envelope.errors.map((e) => e.message ?? "unknown error").join("; ");
|
|
2508
|
+
return new Error(`Linear GraphQL error: ${message}`);
|
|
2509
|
+
}
|
|
2497
2510
|
var LinearGraphQLStub = class {
|
|
2498
2511
|
async query(query, _variables) {
|
|
2499
2512
|
console.log("Linear GraphQL query (stub):", query);
|
|
@@ -4696,18 +4709,18 @@ var AnthropicBackend = class {
|
|
|
4696
4709
|
usage
|
|
4697
4710
|
};
|
|
4698
4711
|
} catch (err) {
|
|
4699
|
-
const
|
|
4712
|
+
const errorMessage2 = err instanceof Error ? err.message : "Anthropic request failed";
|
|
4700
4713
|
yield {
|
|
4701
4714
|
type: "error",
|
|
4702
4715
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4703
|
-
content:
|
|
4716
|
+
content: errorMessage2,
|
|
4704
4717
|
sessionId: session.sessionId
|
|
4705
4718
|
};
|
|
4706
4719
|
return {
|
|
4707
4720
|
success: false,
|
|
4708
4721
|
sessionId: session.sessionId,
|
|
4709
4722
|
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
4710
|
-
error:
|
|
4723
|
+
error: errorMessage2
|
|
4711
4724
|
};
|
|
4712
4725
|
}
|
|
4713
4726
|
}
|
|
@@ -4801,18 +4814,18 @@ var OpenAIBackend = class {
|
|
|
4801
4814
|
}
|
|
4802
4815
|
}
|
|
4803
4816
|
} catch (err) {
|
|
4804
|
-
const
|
|
4817
|
+
const errorMessage2 = err instanceof Error ? err.message : "OpenAI request failed";
|
|
4805
4818
|
yield {
|
|
4806
4819
|
type: "error",
|
|
4807
4820
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4808
|
-
content:
|
|
4821
|
+
content: errorMessage2,
|
|
4809
4822
|
sessionId: session.sessionId
|
|
4810
4823
|
};
|
|
4811
4824
|
return {
|
|
4812
4825
|
success: false,
|
|
4813
4826
|
sessionId: session.sessionId,
|
|
4814
4827
|
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
4815
|
-
error:
|
|
4828
|
+
error: errorMessage2
|
|
4816
4829
|
};
|
|
4817
4830
|
}
|
|
4818
4831
|
const usage = {
|
|
@@ -4926,11 +4939,11 @@ var GeminiBackend = class {
|
|
|
4926
4939
|
}
|
|
4927
4940
|
}
|
|
4928
4941
|
} catch (err) {
|
|
4929
|
-
const
|
|
4942
|
+
const errorMessage2 = err instanceof Error ? err.message : "Gemini request failed";
|
|
4930
4943
|
yield {
|
|
4931
4944
|
type: "error",
|
|
4932
4945
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4933
|
-
content:
|
|
4946
|
+
content: errorMessage2,
|
|
4934
4947
|
sessionId: session.sessionId
|
|
4935
4948
|
};
|
|
4936
4949
|
return {
|
|
@@ -4943,7 +4956,7 @@ var GeminiBackend = class {
|
|
|
4943
4956
|
cacheCreationTokens,
|
|
4944
4957
|
cacheReadTokens
|
|
4945
4958
|
},
|
|
4946
|
-
error:
|
|
4959
|
+
error: errorMessage2
|
|
4947
4960
|
};
|
|
4948
4961
|
}
|
|
4949
4962
|
const usage = {
|
|
@@ -5072,18 +5085,18 @@ var LocalBackend = class {
|
|
|
5072
5085
|
}
|
|
5073
5086
|
}
|
|
5074
5087
|
} catch (err) {
|
|
5075
|
-
const
|
|
5088
|
+
const errorMessage2 = err instanceof Error ? err.message : "Local backend request failed";
|
|
5076
5089
|
yield {
|
|
5077
5090
|
type: "error",
|
|
5078
5091
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5079
|
-
content:
|
|
5092
|
+
content: errorMessage2,
|
|
5080
5093
|
sessionId: session.sessionId
|
|
5081
5094
|
};
|
|
5082
5095
|
return {
|
|
5083
5096
|
success: false,
|
|
5084
5097
|
sessionId: session.sessionId,
|
|
5085
5098
|
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
5086
|
-
error:
|
|
5099
|
+
error: errorMessage2
|
|
5087
5100
|
};
|
|
5088
5101
|
}
|
|
5089
5102
|
const usage = { inputTokens, outputTokens, totalTokens };
|
|
@@ -5425,30 +5438,8 @@ var SshBackend = class {
|
|
|
5425
5438
|
config;
|
|
5426
5439
|
spawnImpl;
|
|
5427
5440
|
constructor(config) {
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
}
|
|
5431
|
-
if (FORBIDDEN_HOST_CHARS.test(config.host) || config.host.startsWith("-")) {
|
|
5432
|
-
throw new Error(
|
|
5433
|
-
`SshBackend: invalid host '${config.host}' (contains shell metacharacters or starts with '-')`
|
|
5434
|
-
);
|
|
5435
|
-
}
|
|
5436
|
-
if (!config.remoteCommand || typeof config.remoteCommand !== "string") {
|
|
5437
|
-
throw new Error("SshBackend: `remoteCommand` is required");
|
|
5438
|
-
}
|
|
5439
|
-
if (config.user !== void 0 && /[\s;&|`$]/.test(config.user)) {
|
|
5440
|
-
throw new Error(`SshBackend: invalid user '${config.user}'`);
|
|
5441
|
-
}
|
|
5442
|
-
this.config = {
|
|
5443
|
-
host: config.host,
|
|
5444
|
-
remoteCommand: config.remoteCommand,
|
|
5445
|
-
sshBinary: config.sshBinary ?? "ssh",
|
|
5446
|
-
sshOptions: config.sshOptions ?? [],
|
|
5447
|
-
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
|
|
5448
|
-
...config.user !== void 0 ? { user: config.user } : {},
|
|
5449
|
-
...config.port !== void 0 ? { port: config.port } : {},
|
|
5450
|
-
...config.identityFile !== void 0 ? { identityFile: config.identityFile } : {}
|
|
5451
|
-
};
|
|
5441
|
+
validateSshConfig(config);
|
|
5442
|
+
this.config = normalizeSshConfig(config);
|
|
5452
5443
|
this.spawnImpl = config.spawnImpl ?? spawn3;
|
|
5453
5444
|
}
|
|
5454
5445
|
/**
|
|
@@ -5600,6 +5591,34 @@ var SshBackend = class {
|
|
|
5600
5591
|
});
|
|
5601
5592
|
}
|
|
5602
5593
|
};
|
|
5594
|
+
function validateSshConfig(config) {
|
|
5595
|
+
if (!config.host || typeof config.host !== "string") {
|
|
5596
|
+
throw new Error("SshBackend: `host` is required");
|
|
5597
|
+
}
|
|
5598
|
+
if (FORBIDDEN_HOST_CHARS.test(config.host) || config.host.startsWith("-")) {
|
|
5599
|
+
throw new Error(
|
|
5600
|
+
`SshBackend: invalid host '${config.host}' (contains shell metacharacters or starts with '-')`
|
|
5601
|
+
);
|
|
5602
|
+
}
|
|
5603
|
+
if (!config.remoteCommand || typeof config.remoteCommand !== "string") {
|
|
5604
|
+
throw new Error("SshBackend: `remoteCommand` is required");
|
|
5605
|
+
}
|
|
5606
|
+
if (config.user !== void 0 && /[\s;&|`$]/.test(config.user)) {
|
|
5607
|
+
throw new Error(`SshBackend: invalid user '${config.user}'`);
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
function normalizeSshConfig(config) {
|
|
5611
|
+
return {
|
|
5612
|
+
host: config.host,
|
|
5613
|
+
remoteCommand: config.remoteCommand,
|
|
5614
|
+
sshBinary: config.sshBinary ?? "ssh",
|
|
5615
|
+
sshOptions: config.sshOptions ?? [],
|
|
5616
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
|
|
5617
|
+
...config.user !== void 0 ? { user: config.user } : {},
|
|
5618
|
+
...config.port !== void 0 ? { port: config.port } : {},
|
|
5619
|
+
...config.identityFile !== void 0 ? { identityFile: config.identityFile } : {}
|
|
5620
|
+
};
|
|
5621
|
+
}
|
|
5603
5622
|
function errResult(sessionId, message) {
|
|
5604
5623
|
return {
|
|
5605
5624
|
success: false,
|
|
@@ -5709,23 +5728,8 @@ var OciServerlessBackend = class extends ServerlessBackend {
|
|
|
5709
5728
|
envSource;
|
|
5710
5729
|
constructor(config) {
|
|
5711
5730
|
super();
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
}
|
|
5715
|
-
if (FORBIDDEN_IMAGE_CHARS.test(config.image) || config.image.startsWith("-")) {
|
|
5716
|
-
throw new Error(
|
|
5717
|
-
`OciServerlessBackend: invalid image '${config.image}' (contains shell metacharacters or starts with '-')`
|
|
5718
|
-
);
|
|
5719
|
-
}
|
|
5720
|
-
this.config = {
|
|
5721
|
-
image: config.image,
|
|
5722
|
-
pullPolicy: config.pullPolicy ?? "if-not-present",
|
|
5723
|
-
runtime: config.runtime ?? "docker",
|
|
5724
|
-
envPassthrough: config.envPassthrough ?? [],
|
|
5725
|
-
timeoutMs: config.timeoutMs ?? DEFAULT_OCI_TIMEOUT_MS,
|
|
5726
|
-
extraArgs: sanitizeExtraArgs(config.extraArgs),
|
|
5727
|
-
...config.registry !== void 0 ? { registry: config.registry } : {}
|
|
5728
|
-
};
|
|
5731
|
+
validateOciImage(config.image);
|
|
5732
|
+
this.config = resolveOciConfig(config);
|
|
5729
5733
|
this.spawnImpl = config.spawnImpl ?? spawn4;
|
|
5730
5734
|
this.envSource = config.envSource ?? process.env;
|
|
5731
5735
|
}
|
|
@@ -5888,6 +5892,27 @@ var OciServerlessBackend = class extends ServerlessBackend {
|
|
|
5888
5892
|
});
|
|
5889
5893
|
}
|
|
5890
5894
|
};
|
|
5895
|
+
function validateOciImage(image) {
|
|
5896
|
+
if (!image || typeof image !== "string") {
|
|
5897
|
+
throw new Error("OciServerlessBackend: `image` is required");
|
|
5898
|
+
}
|
|
5899
|
+
if (FORBIDDEN_IMAGE_CHARS.test(image) || image.startsWith("-")) {
|
|
5900
|
+
throw new Error(
|
|
5901
|
+
`OciServerlessBackend: invalid image '${image}' (contains shell metacharacters or starts with '-')`
|
|
5902
|
+
);
|
|
5903
|
+
}
|
|
5904
|
+
}
|
|
5905
|
+
function resolveOciConfig(config) {
|
|
5906
|
+
return {
|
|
5907
|
+
image: config.image,
|
|
5908
|
+
pullPolicy: config.pullPolicy ?? "if-not-present",
|
|
5909
|
+
runtime: config.runtime ?? "docker",
|
|
5910
|
+
envPassthrough: config.envPassthrough ?? [],
|
|
5911
|
+
timeoutMs: config.timeoutMs ?? DEFAULT_OCI_TIMEOUT_MS,
|
|
5912
|
+
extraArgs: sanitizeExtraArgs(config.extraArgs),
|
|
5913
|
+
...config.registry !== void 0 ? { registry: config.registry } : {}
|
|
5914
|
+
};
|
|
5915
|
+
}
|
|
5891
5916
|
function sanitizeExtraArgs(extraArgs) {
|
|
5892
5917
|
if (!extraArgs) return [];
|
|
5893
5918
|
return extraArgs.filter((arg) => !BLOCKED_DOCKER_FLAGS.some((flag) => arg.startsWith(flag)));
|
|
@@ -5959,78 +5984,96 @@ function makeGetModel(model) {
|
|
|
5959
5984
|
if (Array.isArray(model) && model.length > 0) return () => model[0] ?? null;
|
|
5960
5985
|
return () => null;
|
|
5961
5986
|
}
|
|
5987
|
+
function createClaudeBackend(def, options) {
|
|
5988
|
+
return new ClaudeBackend(def.command ?? "claude", {
|
|
5989
|
+
...options.cacheMetrics ? { cacheMetrics: options.cacheMetrics } : {}
|
|
5990
|
+
});
|
|
5991
|
+
}
|
|
5992
|
+
function createAnthropicBackend(def) {
|
|
5993
|
+
return new AnthropicBackend({
|
|
5994
|
+
model: def.model,
|
|
5995
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
5996
|
+
});
|
|
5997
|
+
}
|
|
5998
|
+
function createOpenAIBackend(def) {
|
|
5999
|
+
return new OpenAIBackend({
|
|
6000
|
+
model: def.model,
|
|
6001
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
6002
|
+
});
|
|
6003
|
+
}
|
|
6004
|
+
function createGeminiBackend(def) {
|
|
6005
|
+
return new GeminiBackend({
|
|
6006
|
+
model: def.model,
|
|
6007
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
6008
|
+
});
|
|
6009
|
+
}
|
|
6010
|
+
function createLocalBackend(def) {
|
|
6011
|
+
const isArray = Array.isArray(def.model);
|
|
6012
|
+
return new LocalBackend({
|
|
6013
|
+
endpoint: def.endpoint,
|
|
6014
|
+
...typeof def.model === "string" ? { model: def.model } : {},
|
|
6015
|
+
...isArray ? { getModel: makeGetModel(def.model) } : {},
|
|
6016
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {},
|
|
6017
|
+
...def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}
|
|
6018
|
+
});
|
|
6019
|
+
}
|
|
6020
|
+
function createPiBackend(def) {
|
|
6021
|
+
const isArray = Array.isArray(def.model);
|
|
6022
|
+
return new PiBackend({
|
|
6023
|
+
endpoint: def.endpoint,
|
|
6024
|
+
...typeof def.model === "string" ? { model: def.model } : {},
|
|
6025
|
+
...isArray ? { getModel: makeGetModel(def.model) } : {},
|
|
6026
|
+
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {},
|
|
6027
|
+
...def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}
|
|
6028
|
+
});
|
|
6029
|
+
}
|
|
6030
|
+
function createSshBackend(def) {
|
|
6031
|
+
return new SshBackend({
|
|
6032
|
+
host: def.host,
|
|
6033
|
+
remoteCommand: def.remoteCommand,
|
|
6034
|
+
...def.user !== void 0 ? { user: def.user } : {},
|
|
6035
|
+
...def.port !== void 0 ? { port: def.port } : {},
|
|
6036
|
+
...def.identityFile !== void 0 ? { identityFile: def.identityFile } : {},
|
|
6037
|
+
...def.sshOptions !== void 0 ? { sshOptions: def.sshOptions } : {},
|
|
6038
|
+
...def.sshBinary !== void 0 ? { sshBinary: def.sshBinary } : {}
|
|
6039
|
+
});
|
|
6040
|
+
}
|
|
6041
|
+
function createServerlessBackend(def) {
|
|
6042
|
+
switch (def.adapter) {
|
|
6043
|
+
case "oci":
|
|
6044
|
+
return new OciServerlessBackend({
|
|
6045
|
+
image: def.image,
|
|
6046
|
+
...def.registry !== void 0 ? { registry: def.registry } : {},
|
|
6047
|
+
...def.pullPolicy !== void 0 ? { pullPolicy: def.pullPolicy } : {},
|
|
6048
|
+
...def.envPassthrough !== void 0 ? { envPassthrough: def.envPassthrough } : {},
|
|
6049
|
+
...def.runtime !== void 0 ? { runtime: def.runtime } : {}
|
|
6050
|
+
});
|
|
6051
|
+
default: {
|
|
6052
|
+
const exhaustive = def.adapter;
|
|
6053
|
+
throw new Error(`createBackend: unknown serverless adapter ${JSON.stringify(exhaustive)}`);
|
|
6054
|
+
}
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
5962
6057
|
function createBackend(def, options = {}) {
|
|
5963
6058
|
switch (def.type) {
|
|
5964
6059
|
case "mock":
|
|
5965
6060
|
return new MockBackend();
|
|
5966
6061
|
case "claude":
|
|
5967
|
-
return
|
|
5968
|
-
...options.cacheMetrics ? { cacheMetrics: options.cacheMetrics } : {}
|
|
5969
|
-
});
|
|
6062
|
+
return createClaudeBackend(def, options);
|
|
5970
6063
|
case "anthropic":
|
|
5971
|
-
return
|
|
5972
|
-
model: def.model,
|
|
5973
|
-
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
5974
|
-
});
|
|
6064
|
+
return createAnthropicBackend(def);
|
|
5975
6065
|
case "openai":
|
|
5976
|
-
return
|
|
5977
|
-
model: def.model,
|
|
5978
|
-
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {}
|
|
5979
|
-
});
|
|
6066
|
+
return createOpenAIBackend(def);
|
|
5980
6067
|
case "gemini":
|
|
5981
|
-
return
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
return
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
...isArray ? { getModel: makeGetModel(def.model) } : {},
|
|
5991
|
-
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {},
|
|
5992
|
-
...def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}
|
|
5993
|
-
});
|
|
5994
|
-
}
|
|
5995
|
-
case "pi": {
|
|
5996
|
-
const isArray = Array.isArray(def.model);
|
|
5997
|
-
return new PiBackend({
|
|
5998
|
-
endpoint: def.endpoint,
|
|
5999
|
-
...typeof def.model === "string" ? { model: def.model } : {},
|
|
6000
|
-
...isArray ? { getModel: makeGetModel(def.model) } : {},
|
|
6001
|
-
...def.apiKey !== void 0 ? { apiKey: def.apiKey } : {},
|
|
6002
|
-
...def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}
|
|
6003
|
-
});
|
|
6004
|
-
}
|
|
6005
|
-
case "ssh": {
|
|
6006
|
-
return new SshBackend({
|
|
6007
|
-
host: def.host,
|
|
6008
|
-
remoteCommand: def.remoteCommand,
|
|
6009
|
-
...def.user !== void 0 ? { user: def.user } : {},
|
|
6010
|
-
...def.port !== void 0 ? { port: def.port } : {},
|
|
6011
|
-
...def.identityFile !== void 0 ? { identityFile: def.identityFile } : {},
|
|
6012
|
-
...def.sshOptions !== void 0 ? { sshOptions: def.sshOptions } : {},
|
|
6013
|
-
...def.sshBinary !== void 0 ? { sshBinary: def.sshBinary } : {}
|
|
6014
|
-
});
|
|
6015
|
-
}
|
|
6016
|
-
case "serverless": {
|
|
6017
|
-
switch (def.adapter) {
|
|
6018
|
-
case "oci":
|
|
6019
|
-
return new OciServerlessBackend({
|
|
6020
|
-
image: def.image,
|
|
6021
|
-
...def.registry !== void 0 ? { registry: def.registry } : {},
|
|
6022
|
-
...def.pullPolicy !== void 0 ? { pullPolicy: def.pullPolicy } : {},
|
|
6023
|
-
...def.envPassthrough !== void 0 ? { envPassthrough: def.envPassthrough } : {},
|
|
6024
|
-
...def.runtime !== void 0 ? { runtime: def.runtime } : {}
|
|
6025
|
-
});
|
|
6026
|
-
default: {
|
|
6027
|
-
const exhaustive = def.adapter;
|
|
6028
|
-
throw new Error(
|
|
6029
|
-
`createBackend: unknown serverless adapter ${JSON.stringify(exhaustive)}`
|
|
6030
|
-
);
|
|
6031
|
-
}
|
|
6032
|
-
}
|
|
6033
|
-
}
|
|
6068
|
+
return createGeminiBackend(def);
|
|
6069
|
+
case "local":
|
|
6070
|
+
return createLocalBackend(def);
|
|
6071
|
+
case "pi":
|
|
6072
|
+
return createPiBackend(def);
|
|
6073
|
+
case "ssh":
|
|
6074
|
+
return createSshBackend(def);
|
|
6075
|
+
case "serverless":
|
|
6076
|
+
return createServerlessBackend(def);
|
|
6034
6077
|
default: {
|
|
6035
6078
|
const exhaustive = def;
|
|
6036
6079
|
throw new Error(`createBackend: unknown backend type ${JSON.stringify(exhaustive)}`);
|
|
@@ -9618,26 +9661,41 @@ function hasScope(held, required) {
|
|
|
9618
9661
|
if (held.includes("admin")) return true;
|
|
9619
9662
|
return held.includes(required);
|
|
9620
9663
|
}
|
|
9621
|
-
function
|
|
9622
|
-
const bridgeScope = requiredBridgeScope(method, path24);
|
|
9623
|
-
if (bridgeScope) return bridgeScope;
|
|
9664
|
+
function exactScopeForRoute(method, path24) {
|
|
9624
9665
|
if (path24 === "/api/v1/auth/token" && method === "POST") return "admin";
|
|
9625
9666
|
if (path24 === "/api/v1/auth/tokens" && method === "GET") return "admin";
|
|
9626
9667
|
if (/^\/api\/v1\/auth\/tokens\/[^/]+$/.test(path24) && method === "DELETE") return "admin";
|
|
9627
9668
|
if ((path24 === "/api/state" || path24 === "/api/v1/state") && method === "GET") return "read-status";
|
|
9628
|
-
if (path24.startsWith("/api/interactions")) return "resolve-interaction";
|
|
9629
|
-
if (path24.startsWith("/api/plans")) return "read-status";
|
|
9630
|
-
if (path24.startsWith("/api/analyze") || path24.startsWith("/api/analyses")) return "read-status";
|
|
9631
|
-
if (path24.startsWith("/api/roadmap-actions")) return "modify-roadmap";
|
|
9632
|
-
if (path24.startsWith("/api/dispatch-actions")) return "trigger-job";
|
|
9633
|
-
if (path24.startsWith("/api/local-model") || path24.startsWith("/api/local-models"))
|
|
9634
|
-
return "read-status";
|
|
9635
|
-
if (path24.startsWith("/api/maintenance")) return "trigger-job";
|
|
9636
|
-
if (path24.startsWith("/api/streams")) return "read-status";
|
|
9637
|
-
if (path24.startsWith("/api/sessions")) return "read-status";
|
|
9638
|
-
if (path24 === "/api/chat" || path24.startsWith("/api/chat-proxy")) return "trigger-job";
|
|
9639
9669
|
return null;
|
|
9640
9670
|
}
|
|
9671
|
+
var PREFIX_SCOPES = [
|
|
9672
|
+
["/api/interactions", "resolve-interaction"],
|
|
9673
|
+
["/api/plans", "read-status"],
|
|
9674
|
+
["/api/analyze", "read-status"],
|
|
9675
|
+
["/api/analyses", "read-status"],
|
|
9676
|
+
["/api/roadmap-actions", "modify-roadmap"],
|
|
9677
|
+
["/api/dispatch-actions", "trigger-job"],
|
|
9678
|
+
["/api/local-model", "read-status"],
|
|
9679
|
+
["/api/local-models", "read-status"],
|
|
9680
|
+
["/api/maintenance", "trigger-job"],
|
|
9681
|
+
["/api/streams", "read-status"],
|
|
9682
|
+
["/api/sessions", "read-status"],
|
|
9683
|
+
["/api/chat-proxy", "trigger-job"]
|
|
9684
|
+
];
|
|
9685
|
+
function prefixScopeForPath(path24) {
|
|
9686
|
+
if (path24 === "/api/chat") return "trigger-job";
|
|
9687
|
+
for (const [prefix, scope] of PREFIX_SCOPES) {
|
|
9688
|
+
if (path24.startsWith(prefix)) return scope;
|
|
9689
|
+
}
|
|
9690
|
+
return null;
|
|
9691
|
+
}
|
|
9692
|
+
function requiredScopeForRoute(method, path24) {
|
|
9693
|
+
const bridgeScope = requiredBridgeScope(method, path24);
|
|
9694
|
+
if (bridgeScope) return bridgeScope;
|
|
9695
|
+
const exactScope = exactScopeForRoute(method, path24);
|
|
9696
|
+
if (exactScope) return exactScope;
|
|
9697
|
+
return prefixScopeForPath(path24);
|
|
9698
|
+
}
|
|
9641
9699
|
|
|
9642
9700
|
// src/server/http.ts
|
|
9643
9701
|
var RATE_LIMIT = Number(process.env["HARNESS_RATE_LIMIT"]) || 100;
|
|
@@ -10596,79 +10654,102 @@ function buildAttributes(payload, extras = {}) {
|
|
|
10596
10654
|
}
|
|
10597
10655
|
return attrs;
|
|
10598
10656
|
}
|
|
10657
|
+
function extractIds(payload) {
|
|
10658
|
+
const correlationId = typeof payload["correlationId"] === "string" ? payload["correlationId"] : void 0;
|
|
10659
|
+
const taskId = typeof payload["taskId"] === "string" ? payload["taskId"] : void 0;
|
|
10660
|
+
return { correlationId, taskId };
|
|
10661
|
+
}
|
|
10662
|
+
function resolveActiveRun(registry, correlationId, taskId) {
|
|
10663
|
+
return registry.resolve({
|
|
10664
|
+
...correlationId !== void 0 ? { correlationId } : {},
|
|
10665
|
+
...taskId !== void 0 ? { taskId } : {}
|
|
10666
|
+
});
|
|
10667
|
+
}
|
|
10668
|
+
function openMaintenanceSpan(registry, correlationId, taskId) {
|
|
10669
|
+
const traceId = newTraceId();
|
|
10670
|
+
const spanId = newSpanId();
|
|
10671
|
+
const key = correlationId ?? taskId ?? `run_${spanId}`;
|
|
10672
|
+
registry.open(key, { traceId, spanId });
|
|
10673
|
+
return { traceId, spanId };
|
|
10674
|
+
}
|
|
10675
|
+
function closeMaintenanceSpan(registry, topic, correlationId, taskId) {
|
|
10676
|
+
const existing = resolveActiveRun(registry, correlationId, taskId);
|
|
10677
|
+
const traceId = existing?.traceId ?? newTraceId();
|
|
10678
|
+
const spanId = existing?.spanId ?? newSpanId();
|
|
10679
|
+
const statusCode = topic === TOPICS.MAINTENANCE_ERROR ? 2 : 1;
|
|
10680
|
+
const key = correlationId ?? taskId ?? "";
|
|
10681
|
+
if (key) registry.close(key);
|
|
10682
|
+
return { traceId, spanId, statusCode };
|
|
10683
|
+
}
|
|
10684
|
+
function childSpan(registry, correlationId, taskId) {
|
|
10685
|
+
const parent = resolveActiveRun(registry, correlationId, taskId);
|
|
10686
|
+
const plan = {
|
|
10687
|
+
traceId: parent?.traceId ?? newTraceId(),
|
|
10688
|
+
spanId: newSpanId()
|
|
10689
|
+
};
|
|
10690
|
+
if (parent?.spanId !== void 0) plan.parentSpanId = parent.spanId;
|
|
10691
|
+
return plan;
|
|
10692
|
+
}
|
|
10693
|
+
function planSpan(registry, topic, correlationId, taskId) {
|
|
10694
|
+
if (topic === TOPICS.MAINTENANCE_STARTED) {
|
|
10695
|
+
return openMaintenanceSpan(registry, correlationId, taskId);
|
|
10696
|
+
}
|
|
10697
|
+
if (topic === TOPICS.MAINTENANCE_COMPLETED || topic === TOPICS.MAINTENANCE_ERROR) {
|
|
10698
|
+
return closeMaintenanceSpan(registry, topic, correlationId, taskId);
|
|
10699
|
+
}
|
|
10700
|
+
return childSpan(registry, correlationId, taskId);
|
|
10701
|
+
}
|
|
10702
|
+
function buildSpan(topic, plan, payload) {
|
|
10703
|
+
const startNs = nowNs();
|
|
10704
|
+
return {
|
|
10705
|
+
traceId: plan.traceId,
|
|
10706
|
+
spanId: plan.spanId,
|
|
10707
|
+
...plan.parentSpanId !== void 0 ? { parentSpanId: plan.parentSpanId } : {},
|
|
10708
|
+
name: SPAN_NAME[topic],
|
|
10709
|
+
kind: SpanKind.INTERNAL,
|
|
10710
|
+
startTimeNs: startNs,
|
|
10711
|
+
endTimeNs: startNs,
|
|
10712
|
+
attributes: buildAttributes(payload, { "harness.topic": topic }),
|
|
10713
|
+
...plan.statusCode !== void 0 ? { statusCode: plan.statusCode } : {}
|
|
10714
|
+
};
|
|
10715
|
+
}
|
|
10716
|
+
function buildGatewayEvent(topic, payload, correlationId) {
|
|
10717
|
+
return {
|
|
10718
|
+
id: newEventId2(),
|
|
10719
|
+
type: TELEMETRY_TYPE[topic],
|
|
10720
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10721
|
+
data: payload,
|
|
10722
|
+
...correlationId !== void 0 ? { correlationId } : {}
|
|
10723
|
+
};
|
|
10724
|
+
}
|
|
10725
|
+
async function enqueueToMatchingSubs(store, webhookDelivery, event) {
|
|
10726
|
+
const subs = await store.listForEvent(event.type);
|
|
10727
|
+
if (subs.length === 0) return;
|
|
10728
|
+
const filtered = subs.filter((sub) => sub.events.some((p) => eventMatches(p, event.type)));
|
|
10729
|
+
for (const sub of filtered) {
|
|
10730
|
+
webhookDelivery.enqueue(sub, event);
|
|
10731
|
+
}
|
|
10732
|
+
}
|
|
10733
|
+
function makeTelemetryHandler(ctx, topic) {
|
|
10734
|
+
return (data) => {
|
|
10735
|
+
const payload = data ?? {};
|
|
10736
|
+
const { correlationId, taskId } = extractIds(payload);
|
|
10737
|
+
const plan = planSpan(ctx.registry, topic, correlationId, taskId);
|
|
10738
|
+
ctx.exporter.push(buildSpan(topic, plan, payload));
|
|
10739
|
+
void enqueueToMatchingSubs(
|
|
10740
|
+
ctx.store,
|
|
10741
|
+
ctx.webhookDelivery,
|
|
10742
|
+
buildGatewayEvent(topic, payload, correlationId)
|
|
10743
|
+
);
|
|
10744
|
+
};
|
|
10745
|
+
}
|
|
10599
10746
|
function wireTelemetryFanout(params) {
|
|
10600
10747
|
const { bus, exporter, webhookDelivery, store } = params;
|
|
10601
10748
|
const registry = new ActiveRunRegistry();
|
|
10602
10749
|
const handlers = [];
|
|
10603
|
-
const
|
|
10604
|
-
const subs = await store.listForEvent(event.type);
|
|
10605
|
-
if (subs.length === 0) return;
|
|
10606
|
-
const filtered = subs.filter((sub) => sub.events.some((p) => eventMatches(p, event.type)));
|
|
10607
|
-
for (const sub of filtered) {
|
|
10608
|
-
webhookDelivery.enqueue(sub, event);
|
|
10609
|
-
}
|
|
10610
|
-
};
|
|
10611
|
-
const makeHandler = (topic) => (data) => {
|
|
10612
|
-
const payload = data ?? {};
|
|
10613
|
-
const correlationId = typeof payload["correlationId"] === "string" ? payload["correlationId"] : void 0;
|
|
10614
|
-
const taskId = typeof payload["taskId"] === "string" ? payload["taskId"] : void 0;
|
|
10615
|
-
let traceId;
|
|
10616
|
-
let spanId;
|
|
10617
|
-
let parentSpanId;
|
|
10618
|
-
let statusCode;
|
|
10619
|
-
if (topic === TOPICS.MAINTENANCE_STARTED) {
|
|
10620
|
-
traceId = newTraceId();
|
|
10621
|
-
spanId = newSpanId();
|
|
10622
|
-
const key = correlationId ?? taskId ?? `run_${spanId}`;
|
|
10623
|
-
registry.open(key, { traceId, spanId });
|
|
10624
|
-
} else if (topic === TOPICS.MAINTENANCE_COMPLETED || topic === TOPICS.MAINTENANCE_ERROR) {
|
|
10625
|
-
const existing = registry.resolve({
|
|
10626
|
-
...correlationId !== void 0 ? { correlationId } : {},
|
|
10627
|
-
...taskId !== void 0 ? { taskId } : {}
|
|
10628
|
-
});
|
|
10629
|
-
if (existing !== void 0) {
|
|
10630
|
-
traceId = existing.traceId;
|
|
10631
|
-
spanId = existing.spanId;
|
|
10632
|
-
} else {
|
|
10633
|
-
traceId = newTraceId();
|
|
10634
|
-
spanId = newSpanId();
|
|
10635
|
-
}
|
|
10636
|
-
statusCode = topic === TOPICS.MAINTENANCE_ERROR ? 2 : 1;
|
|
10637
|
-
const key = correlationId ?? taskId ?? "";
|
|
10638
|
-
if (key) registry.close(key);
|
|
10639
|
-
} else {
|
|
10640
|
-
const parent = registry.resolve({
|
|
10641
|
-
...correlationId !== void 0 ? { correlationId } : {},
|
|
10642
|
-
...taskId !== void 0 ? { taskId } : {}
|
|
10643
|
-
});
|
|
10644
|
-
traceId = parent?.traceId ?? newTraceId();
|
|
10645
|
-
spanId = newSpanId();
|
|
10646
|
-
parentSpanId = parent?.spanId;
|
|
10647
|
-
}
|
|
10648
|
-
const startNs = nowNs();
|
|
10649
|
-
const span = {
|
|
10650
|
-
traceId,
|
|
10651
|
-
spanId,
|
|
10652
|
-
...parentSpanId !== void 0 ? { parentSpanId } : {},
|
|
10653
|
-
name: SPAN_NAME[topic],
|
|
10654
|
-
kind: SpanKind.INTERNAL,
|
|
10655
|
-
startTimeNs: startNs,
|
|
10656
|
-
endTimeNs: startNs,
|
|
10657
|
-
attributes: buildAttributes(payload, { "harness.topic": topic }),
|
|
10658
|
-
...statusCode !== void 0 ? { statusCode } : {}
|
|
10659
|
-
};
|
|
10660
|
-
exporter.push(span);
|
|
10661
|
-
const gatewayEvent = {
|
|
10662
|
-
id: newEventId2(),
|
|
10663
|
-
type: TELEMETRY_TYPE[topic],
|
|
10664
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10665
|
-
data: payload,
|
|
10666
|
-
...correlationId !== void 0 ? { correlationId } : {}
|
|
10667
|
-
};
|
|
10668
|
-
void enqueueToMatchingSubs(gatewayEvent);
|
|
10669
|
-
};
|
|
10750
|
+
const ctx = { registry, exporter, webhookDelivery, store };
|
|
10670
10751
|
for (const topic of Object.values(TOPICS)) {
|
|
10671
|
-
const fn =
|
|
10752
|
+
const fn = makeTelemetryHandler(ctx, topic);
|
|
10672
10753
|
bus.on(topic, fn);
|
|
10673
10754
|
handlers.push({ topic, fn });
|
|
10674
10755
|
}
|
|
@@ -12371,39 +12452,50 @@ function classifyCheckExecutionFailure(output) {
|
|
|
12371
12452
|
function parseStatusLine(output) {
|
|
12372
12453
|
const lines = output.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
12373
12454
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12374
|
-
const
|
|
12375
|
-
if (
|
|
12376
|
-
try {
|
|
12377
|
-
const obj = JSON.parse(line);
|
|
12378
|
-
const s = obj.status;
|
|
12379
|
-
if (s === "success" || s === "skipped" || s === "failure" || s === "no-issues") {
|
|
12380
|
-
const parsed = { status: s, rawStatus: s };
|
|
12381
|
-
if (typeof obj.candidatesFound === "number") {
|
|
12382
|
-
parsed.candidatesFound = obj.candidatesFound;
|
|
12383
|
-
}
|
|
12384
|
-
if (typeof obj.error === "string") {
|
|
12385
|
-
parsed.error = obj.error;
|
|
12386
|
-
}
|
|
12387
|
-
if (typeof obj.reason === "string") {
|
|
12388
|
-
parsed.reason = obj.reason;
|
|
12389
|
-
}
|
|
12390
|
-
if (typeof obj.detail === "string" && !parsed.error) {
|
|
12391
|
-
parsed.error = `${parsed.reason ?? "skipped"}: ${obj.detail}`;
|
|
12392
|
-
}
|
|
12393
|
-
return parsed;
|
|
12394
|
-
}
|
|
12395
|
-
if (s === "updated" || s === "no-op") {
|
|
12396
|
-
return { status: "success", rawStatus: s };
|
|
12397
|
-
}
|
|
12398
|
-
if (s === "error") {
|
|
12399
|
-
const message = typeof obj.message === "string" ? obj.message : "unknown error";
|
|
12400
|
-
return { status: "failure", error: message, rawStatus: "error" };
|
|
12401
|
-
}
|
|
12402
|
-
} catch {
|
|
12403
|
-
}
|
|
12455
|
+
const parsed = parseStatusObjectLine(lines[i]);
|
|
12456
|
+
if (parsed) return parsed;
|
|
12404
12457
|
}
|
|
12405
12458
|
return null;
|
|
12406
12459
|
}
|
|
12460
|
+
function parseStatusObjectLine(line) {
|
|
12461
|
+
if (!line || !line.startsWith("{") || !line.endsWith("}")) return null;
|
|
12462
|
+
let obj;
|
|
12463
|
+
try {
|
|
12464
|
+
obj = JSON.parse(line);
|
|
12465
|
+
} catch {
|
|
12466
|
+
return null;
|
|
12467
|
+
}
|
|
12468
|
+
return classifyStatusObject(obj);
|
|
12469
|
+
}
|
|
12470
|
+
var PHASE_STATUSES = /* @__PURE__ */ new Set(["success", "skipped", "failure", "no-issues"]);
|
|
12471
|
+
var SYNC_SUCCESS_STATUSES = /* @__PURE__ */ new Set(["updated", "no-op"]);
|
|
12472
|
+
function classifyStatusObject(obj) {
|
|
12473
|
+
const s = obj.status;
|
|
12474
|
+
if (typeof s !== "string") return null;
|
|
12475
|
+
if (PHASE_STATUSES.has(s)) return buildPhaseStatus(s, obj);
|
|
12476
|
+
if (SYNC_SUCCESS_STATUSES.has(s)) return { status: "success", rawStatus: s };
|
|
12477
|
+
if (s === "error") {
|
|
12478
|
+
const message = typeof obj.message === "string" ? obj.message : "unknown error";
|
|
12479
|
+
return { status: "failure", error: message, rawStatus: "error" };
|
|
12480
|
+
}
|
|
12481
|
+
return null;
|
|
12482
|
+
}
|
|
12483
|
+
function buildPhaseStatus(status, obj) {
|
|
12484
|
+
const parsed = { status, rawStatus: status };
|
|
12485
|
+
if (typeof obj.candidatesFound === "number") {
|
|
12486
|
+
parsed.candidatesFound = obj.candidatesFound;
|
|
12487
|
+
}
|
|
12488
|
+
if (typeof obj.error === "string") {
|
|
12489
|
+
parsed.error = obj.error;
|
|
12490
|
+
}
|
|
12491
|
+
if (typeof obj.reason === "string") {
|
|
12492
|
+
parsed.reason = obj.reason;
|
|
12493
|
+
}
|
|
12494
|
+
if (typeof obj.detail === "string" && !parsed.error) {
|
|
12495
|
+
parsed.error = `${parsed.reason ?? "skipped"}: ${obj.detail}`;
|
|
12496
|
+
}
|
|
12497
|
+
return parsed;
|
|
12498
|
+
}
|
|
12407
12499
|
|
|
12408
12500
|
// src/maintenance/check-script-runner.ts
|
|
12409
12501
|
import { execFile as execFile6 } from "child_process";
|
|
@@ -13298,22 +13390,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
13298
13390
|
});
|
|
13299
13391
|
webhookDelivery.start();
|
|
13300
13392
|
this.setupNotifications(config.notifications);
|
|
13301
|
-
|
|
13302
|
-
if (otlpCfg) {
|
|
13303
|
-
this.otlpExporter = new OTLPExporter({
|
|
13304
|
-
endpoint: otlpCfg.endpoint,
|
|
13305
|
-
...otlpCfg.enabled !== void 0 ? { enabled: otlpCfg.enabled } : {},
|
|
13306
|
-
...otlpCfg.headers !== void 0 ? { headers: otlpCfg.headers } : {},
|
|
13307
|
-
...otlpCfg.flushIntervalMs !== void 0 ? { flushIntervalMs: otlpCfg.flushIntervalMs } : {},
|
|
13308
|
-
...otlpCfg.batchSize !== void 0 ? { batchSize: otlpCfg.batchSize } : {}
|
|
13309
|
-
});
|
|
13310
|
-
this.telemetryFanoutOff = wireTelemetryFanout({
|
|
13311
|
-
bus: this,
|
|
13312
|
-
exporter: this.otlpExporter,
|
|
13313
|
-
webhookDelivery,
|
|
13314
|
-
store: webhookStore
|
|
13315
|
-
});
|
|
13316
|
-
}
|
|
13393
|
+
this.setupTelemetryExport(config, webhookStore, webhookDelivery);
|
|
13317
13394
|
this.server = new OrchestratorServer(this, config.server.port, {
|
|
13318
13395
|
interactionQueue: this.interactionQueue,
|
|
13319
13396
|
webhooks: {
|
|
@@ -13336,24 +13413,8 @@ var Orchestrator = class extends EventEmitter {
|
|
|
13336
13413
|
analysisArchive: this.analysisArchive,
|
|
13337
13414
|
roadmapPath: config.tracker.filePath ?? null,
|
|
13338
13415
|
dispatchAdHoc: this.dispatchAdHoc.bind(this),
|
|
13339
|
-
getLocalModelStatus: () =>
|
|
13340
|
-
|
|
13341
|
-
return first.done ? null : first.value.getStatus();
|
|
13342
|
-
},
|
|
13343
|
-
getLocalModelStatuses: () => {
|
|
13344
|
-
const backends = this.config.agent.backends ?? {};
|
|
13345
|
-
const out = [];
|
|
13346
|
-
for (const [name, resolver] of this.localResolvers) {
|
|
13347
|
-
const def = backends[name];
|
|
13348
|
-
if (!def || def.type !== "local" && def.type !== "pi") continue;
|
|
13349
|
-
out.push({
|
|
13350
|
-
...resolver.getStatus(),
|
|
13351
|
-
backendName: name,
|
|
13352
|
-
endpoint: def.endpoint
|
|
13353
|
-
});
|
|
13354
|
-
}
|
|
13355
|
-
return out;
|
|
13356
|
-
}
|
|
13416
|
+
getLocalModelStatus: () => this.getFirstLocalModelStatus(),
|
|
13417
|
+
getLocalModelStatuses: () => this.buildLocalModelStatuses()
|
|
13357
13418
|
});
|
|
13358
13419
|
this.server.setRecorder(this.recorder);
|
|
13359
13420
|
this.interactionQueue.onPush((interaction) => {
|
|
@@ -13361,6 +13422,56 @@ var Orchestrator = class extends EventEmitter {
|
|
|
13361
13422
|
});
|
|
13362
13423
|
}
|
|
13363
13424
|
}
|
|
13425
|
+
/**
|
|
13426
|
+
* Phase 5: construct the OTLP/HTTP trace exporter and wire telemetry fanout.
|
|
13427
|
+
* Only fires when the operator configures `telemetry.export.otlp` in
|
|
13428
|
+
* harness.config.json. Extracted from the server-init block in the
|
|
13429
|
+
* constructor to keep that block's cyclomatic complexity under threshold.
|
|
13430
|
+
*/
|
|
13431
|
+
setupTelemetryExport(config, webhookStore, webhookDelivery) {
|
|
13432
|
+
const otlpCfg = config.telemetry?.export?.otlp;
|
|
13433
|
+
if (!otlpCfg) return;
|
|
13434
|
+
this.otlpExporter = new OTLPExporter({
|
|
13435
|
+
endpoint: otlpCfg.endpoint,
|
|
13436
|
+
...otlpCfg.enabled !== void 0 ? { enabled: otlpCfg.enabled } : {},
|
|
13437
|
+
...otlpCfg.headers !== void 0 ? { headers: otlpCfg.headers } : {},
|
|
13438
|
+
...otlpCfg.flushIntervalMs !== void 0 ? { flushIntervalMs: otlpCfg.flushIntervalMs } : {},
|
|
13439
|
+
...otlpCfg.batchSize !== void 0 ? { batchSize: otlpCfg.batchSize } : {}
|
|
13440
|
+
});
|
|
13441
|
+
this.telemetryFanoutOff = wireTelemetryFanout({
|
|
13442
|
+
bus: this,
|
|
13443
|
+
exporter: this.otlpExporter,
|
|
13444
|
+
webhookDelivery,
|
|
13445
|
+
store: webhookStore
|
|
13446
|
+
});
|
|
13447
|
+
}
|
|
13448
|
+
/**
|
|
13449
|
+
* Deprecated alias for /api/v1/local-model/status (Spec 1 endpoint retained
|
|
13450
|
+
* as a compat shim per spec line 35; superseded by getLocalModelStatuses for
|
|
13451
|
+
* the multi-local UI). Returns the first-registered resolver's status.
|
|
13452
|
+
*/
|
|
13453
|
+
getFirstLocalModelStatus() {
|
|
13454
|
+
const first = this.localResolvers.values().next();
|
|
13455
|
+
return first.done ? null : first.value.getStatus();
|
|
13456
|
+
}
|
|
13457
|
+
/**
|
|
13458
|
+
* SC38: build NamedLocalModelStatus[] from each registered resolver, tagged
|
|
13459
|
+
* with its backendName + endpoint from the config.
|
|
13460
|
+
*/
|
|
13461
|
+
buildLocalModelStatuses() {
|
|
13462
|
+
const backends = this.config.agent.backends ?? {};
|
|
13463
|
+
const out = [];
|
|
13464
|
+
for (const [name, resolver] of this.localResolvers) {
|
|
13465
|
+
const def = backends[name];
|
|
13466
|
+
if (!def || def.type !== "local" && def.type !== "pi") continue;
|
|
13467
|
+
out.push({
|
|
13468
|
+
...resolver.getStatus(),
|
|
13469
|
+
backendName: name,
|
|
13470
|
+
endpoint: def.endpoint
|
|
13471
|
+
});
|
|
13472
|
+
}
|
|
13473
|
+
return out;
|
|
13474
|
+
}
|
|
13364
13475
|
createTracker() {
|
|
13365
13476
|
if (this.config.tracker.kind === "github-issues") {
|
|
13366
13477
|
const trackerCfg = {
|