@hilbras/omninode 2.0.0-alpha.1 → 2.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -10
- package/dist/cli/index.js +1580 -280
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +896 -105
- package/dist/index.js +1575 -255
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// src/types/task.ts
|
|
2
|
+
var TERMINAL_TASK_STATUSES = [
|
|
3
|
+
"completed",
|
|
4
|
+
"failed",
|
|
5
|
+
"timed_out",
|
|
6
|
+
"cancelled",
|
|
7
|
+
"partially_completed"
|
|
8
|
+
];
|
|
9
|
+
|
|
1
10
|
// src/errors/index.ts
|
|
2
11
|
var OmniNodeError = class extends Error {
|
|
3
12
|
code;
|
|
@@ -116,6 +125,7 @@ var providerConfigSchema = z.object({
|
|
|
116
125
|
type: z.enum(["openai-compatible", "omnihilbras", "openrouter", "local", "custom"]),
|
|
117
126
|
base_url: z.string().url(),
|
|
118
127
|
api_key_env_var: z.string().min(1).optional(),
|
|
128
|
+
timeout_ms: z.number().int().positive().optional(),
|
|
119
129
|
headers: z.record(z.string()).optional(),
|
|
120
130
|
enabled: z.boolean().optional(),
|
|
121
131
|
metadata: z.record(z.unknown()).optional()
|
|
@@ -131,6 +141,11 @@ var agentConfigSchema = z.object({
|
|
|
131
141
|
input_mode: z.enum(["stdin", "arg", "protocol"]).default("stdin"),
|
|
132
142
|
timeout_ms: z.number().int().positive().optional(),
|
|
133
143
|
inherit_env: z.boolean().optional(),
|
|
144
|
+
env_policy: z.enum(["inherit", "allowlist", "denylist", "explicit"]).optional(),
|
|
145
|
+
env_allowlist: z.array(z.string()).optional(),
|
|
146
|
+
env_denylist: z.array(z.string()).optional(),
|
|
147
|
+
max_output_bytes: z.number().int().positive().optional(),
|
|
148
|
+
allow_external_cwd: z.boolean().optional(),
|
|
134
149
|
metadata: z.record(z.unknown()).optional()
|
|
135
150
|
}).strict();
|
|
136
151
|
var roleConfigSchema = z.object({
|
|
@@ -175,7 +190,9 @@ var projectConfigSchema = z.object({
|
|
|
175
190
|
memory: z.object({
|
|
176
191
|
provider: z.string().min(1).default("local"),
|
|
177
192
|
base_url: z.string().url().optional(),
|
|
178
|
-
api_key_env_var: z.string().min(1).optional()
|
|
193
|
+
api_key_env_var: z.string().min(1).optional(),
|
|
194
|
+
/** When true, memory failures fail tasks instead of being tolerated (§12). */
|
|
195
|
+
required: z.boolean().optional()
|
|
179
196
|
}).strict().optional()
|
|
180
197
|
}).strict();
|
|
181
198
|
var appConfigSchema = z.object({
|
|
@@ -257,7 +274,8 @@ function toAppConfig(yaml) {
|
|
|
257
274
|
memory: {
|
|
258
275
|
provider: yaml.project.memory.provider,
|
|
259
276
|
baseUrl: yaml.project.memory.base_url,
|
|
260
|
-
apiKeyEnvVar: yaml.project.memory.api_key_env_var
|
|
277
|
+
apiKeyEnvVar: yaml.project.memory.api_key_env_var,
|
|
278
|
+
required: yaml.project.memory.required
|
|
261
279
|
}
|
|
262
280
|
} : {}
|
|
263
281
|
},
|
|
@@ -288,6 +306,7 @@ function toProviderConfig(raw) {
|
|
|
288
306
|
type: raw.type,
|
|
289
307
|
baseUrl: raw.base_url,
|
|
290
308
|
apiKeyEnvVar: raw.api_key_env_var,
|
|
309
|
+
timeoutMs: raw.timeout_ms,
|
|
291
310
|
headers: raw.headers,
|
|
292
311
|
enabled: raw.enabled,
|
|
293
312
|
metadata: raw.metadata
|
|
@@ -305,6 +324,11 @@ function toAgentConfig(raw) {
|
|
|
305
324
|
inputMode: raw.input_mode,
|
|
306
325
|
timeoutMs: raw.timeout_ms,
|
|
307
326
|
inheritEnv: raw.inherit_env,
|
|
327
|
+
envPolicy: raw.env_policy,
|
|
328
|
+
envAllowlist: raw.env_allowlist,
|
|
329
|
+
envDenylist: raw.env_denylist,
|
|
330
|
+
maxOutputBytes: raw.max_output_bytes,
|
|
331
|
+
allowExternalCwd: raw.allow_external_cwd,
|
|
308
332
|
metadata: raw.metadata
|
|
309
333
|
};
|
|
310
334
|
}
|
|
@@ -375,57 +399,200 @@ function authHeaders(config, env = process.env) {
|
|
|
375
399
|
}
|
|
376
400
|
|
|
377
401
|
// src/providers/http.ts
|
|
402
|
+
var HttpResponseError = class extends Error {
|
|
403
|
+
constructor(message, status, cause) {
|
|
404
|
+
super(message, { cause });
|
|
405
|
+
this.status = status;
|
|
406
|
+
this.name = "HttpResponseError";
|
|
407
|
+
}
|
|
408
|
+
status;
|
|
409
|
+
};
|
|
378
410
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
379
411
|
async function requestJson(url, options = {}) {
|
|
380
412
|
const { method = "GET", headers = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = options;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
392
|
-
});
|
|
393
|
-
} catch (error) {
|
|
394
|
-
const reason = error instanceof Error && error.name === "TimeoutError" ? `timed out after ${timeoutMs}ms` : error instanceof Error ? error.message : String(error);
|
|
395
|
-
throw new ProviderError("PROVIDER_UNAVAILABLE", `Request to ${url} failed: ${reason}.`, {
|
|
396
|
-
cause: error
|
|
397
|
-
});
|
|
398
|
-
}
|
|
413
|
+
const response = await fetch(url, {
|
|
414
|
+
method,
|
|
415
|
+
headers: {
|
|
416
|
+
accept: "application/json",
|
|
417
|
+
...body !== void 0 ? { "content-type": "application/json" } : {},
|
|
418
|
+
...headers
|
|
419
|
+
},
|
|
420
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
421
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
422
|
+
});
|
|
399
423
|
let parsed;
|
|
400
424
|
try {
|
|
401
425
|
parsed = await response.json();
|
|
402
426
|
} catch (error) {
|
|
403
|
-
throw new
|
|
404
|
-
cause: error,
|
|
405
|
-
details: { status: response.status }
|
|
406
|
-
});
|
|
427
|
+
throw new HttpResponseError(`Response from ${url} was not valid JSON.`, response.status, error);
|
|
407
428
|
}
|
|
408
429
|
return { status: response.status, body: parsed };
|
|
409
430
|
}
|
|
410
431
|
|
|
432
|
+
// src/providers/errors.ts
|
|
433
|
+
function kindToCode(kind) {
|
|
434
|
+
switch (kind) {
|
|
435
|
+
case "AUTHENTICATION_ERROR":
|
|
436
|
+
return "PROVIDER_AUTH_FAILED";
|
|
437
|
+
case "MODEL_NOT_FOUND":
|
|
438
|
+
return "MODEL_NOT_FOUND";
|
|
439
|
+
default:
|
|
440
|
+
return "PROVIDER_UNAVAILABLE";
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function isRetryable(kind) {
|
|
444
|
+
return kind === "RATE_LIMIT_ERROR" || kind === "SERVER_ERROR" || kind === "NETWORK_ERROR";
|
|
445
|
+
}
|
|
446
|
+
function kindFromStatus(status) {
|
|
447
|
+
if (status === 401 || status === 403) return "AUTHENTICATION_ERROR";
|
|
448
|
+
if (status === 429) return "RATE_LIMIT_ERROR";
|
|
449
|
+
if (status === 404) return "MODEL_NOT_FOUND";
|
|
450
|
+
if (status === 408 || status === 504) return "TIMEOUT";
|
|
451
|
+
if (status >= 500) return "SERVER_ERROR";
|
|
452
|
+
if (status >= 400) return "INVALID_REQUEST";
|
|
453
|
+
return "UNKNOWN_ERROR";
|
|
454
|
+
}
|
|
455
|
+
function extractMessage(body) {
|
|
456
|
+
if (body === null || typeof body !== "object") return void 0;
|
|
457
|
+
const root = body;
|
|
458
|
+
const error = root.error ?? root;
|
|
459
|
+
const message = error.message ?? error.detail ?? root.message;
|
|
460
|
+
return typeof message === "string" ? message : void 0;
|
|
461
|
+
}
|
|
462
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
463
|
+
if (!value) return void 0;
|
|
464
|
+
const seconds = Number(value);
|
|
465
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
466
|
+
const date = Date.parse(value);
|
|
467
|
+
return Number.isNaN(date) ? void 0 : Math.max(0, date - now);
|
|
468
|
+
}
|
|
469
|
+
function isTimeoutError(error) {
|
|
470
|
+
return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError") || typeof error?.code === "string" && error.code === "ABORT_ERR";
|
|
471
|
+
}
|
|
472
|
+
function isNetworkError(error) {
|
|
473
|
+
return error instanceof TypeError || typeof error?.cause === "object" && error instanceof Error && !error.code;
|
|
474
|
+
}
|
|
475
|
+
function fallbackMessage(kind, context, status) {
|
|
476
|
+
const { provider, operation } = context;
|
|
477
|
+
switch (kind) {
|
|
478
|
+
case "AUTHENTICATION_ERROR":
|
|
479
|
+
return `Authentication with provider "${provider}" failed (HTTP ${status}). Check that the referenced API key is set and valid.`;
|
|
480
|
+
case "RATE_LIMIT_ERROR":
|
|
481
|
+
return `Provider "${provider}" rate-limited ${operation} (HTTP ${status}). Retry later.`;
|
|
482
|
+
case "MODEL_NOT_FOUND":
|
|
483
|
+
return `Provider "${provider}" does not expose the requested model (HTTP ${status}).`;
|
|
484
|
+
case "TIMEOUT":
|
|
485
|
+
return `Provider "${provider}" timed out during ${operation} (HTTP ${status}).`;
|
|
486
|
+
case "SERVER_ERROR":
|
|
487
|
+
return `Provider "${provider}" returned a server error during ${operation} (HTTP ${status}).`;
|
|
488
|
+
case "INVALID_REQUEST":
|
|
489
|
+
return `Provider "${provider}" rejected ${operation} as invalid (HTTP ${status}).`;
|
|
490
|
+
default:
|
|
491
|
+
return `Provider "${provider}" failed during ${operation} (HTTP ${status}).`;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function providerHttpError(context) {
|
|
495
|
+
const status = context.status ?? 0;
|
|
496
|
+
const kind = kindFromStatus(status);
|
|
497
|
+
const detail = extractMessage(context.body);
|
|
498
|
+
const retryAfterMs = context.retryAfterMs ?? parseRetryAfter(void 0);
|
|
499
|
+
return new ProviderError(
|
|
500
|
+
context.code ?? kindToCode(kind),
|
|
501
|
+
detail ?? fallbackMessage(kind, context, status),
|
|
502
|
+
{
|
|
503
|
+
cause: context.cause,
|
|
504
|
+
details: {
|
|
505
|
+
kind,
|
|
506
|
+
provider: context.provider,
|
|
507
|
+
operation: context.operation,
|
|
508
|
+
status,
|
|
509
|
+
retryable: isRetryable(kind),
|
|
510
|
+
...retryAfterMs !== void 0 ? { retryAfterMs } : {},
|
|
511
|
+
...detail ? { providerMessage: detail } : {}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
function providerTransportError(context) {
|
|
517
|
+
const cause = context.cause;
|
|
518
|
+
const kind = isTimeoutError(cause) ? "TIMEOUT" : cause instanceof SyntaxError || cause?.name === "HttpResponseError" ? "UNKNOWN_ERROR" : "NETWORK_ERROR";
|
|
519
|
+
const message = context.cause instanceof Error ? context.cause.message : String(context.cause);
|
|
520
|
+
return new ProviderError("PROVIDER_UNAVAILABLE", `Provider request failed: ${message}`, {
|
|
521
|
+
cause: context.cause,
|
|
522
|
+
details: {
|
|
523
|
+
kind,
|
|
524
|
+
provider: context.provider,
|
|
525
|
+
operation: context.operation,
|
|
526
|
+
retryable: isRetryable(kind)
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
411
531
|
// src/providers/openai-compatible/index.ts
|
|
532
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
412
533
|
var OpenAICompatibleProvider = class {
|
|
413
534
|
config;
|
|
535
|
+
providerId;
|
|
536
|
+
authentication;
|
|
414
537
|
log;
|
|
415
|
-
|
|
538
|
+
/** Discovered models, so getModel() answers without another round-trip. */
|
|
539
|
+
discovered;
|
|
540
|
+
env;
|
|
541
|
+
constructor(config, log = logger, env = process.env) {
|
|
416
542
|
this.config = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
|
|
543
|
+
this.env = env;
|
|
544
|
+
this.providerId = config.providerId ?? config.name;
|
|
545
|
+
this.authentication = {
|
|
546
|
+
method: config.apiKeyEnvVar ? "bearer" : "none",
|
|
547
|
+
...config.apiKeyEnvVar ? { envVar: config.apiKeyEnvVar } : {},
|
|
548
|
+
configured: config.apiKeyEnvVar ? Boolean(env[config.apiKeyEnvVar]) : true
|
|
549
|
+
};
|
|
417
550
|
this.log = log.child({ provider: config.name });
|
|
418
551
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
552
|
+
/** Provider-level request timeout (roadmap §6.6/§11) with the shared default. */
|
|
553
|
+
get requestTimeoutMs() {
|
|
554
|
+
return this.config.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
555
|
+
}
|
|
556
|
+
/** Authenticate + discover (roadmap §10, Model Discovery). */
|
|
557
|
+
async connect() {
|
|
558
|
+
authHeaders(this.config, this.env);
|
|
559
|
+
const models = await this.listModels();
|
|
560
|
+
this.discovered = models;
|
|
561
|
+
return models;
|
|
562
|
+
}
|
|
563
|
+
async getModel(modelId) {
|
|
564
|
+
const models = this.discovered ?? await this.connect();
|
|
565
|
+
return models.find((m) => m.id === modelId);
|
|
566
|
+
}
|
|
567
|
+
/** Raw /models body (protected so adapters can capture gateway-level metadata). */
|
|
568
|
+
async rawModelsResponse() {
|
|
569
|
+
const response = await this.modelsRequest();
|
|
570
|
+
return response.body;
|
|
571
|
+
}
|
|
572
|
+
async modelsRequest() {
|
|
573
|
+
let response;
|
|
574
|
+
try {
|
|
575
|
+
response = await requestJson(`${this.config.baseUrl}/models`, {
|
|
576
|
+
headers: authHeaders(this.config, this.env),
|
|
577
|
+
timeoutMs: this.requestTimeoutMs
|
|
578
|
+
});
|
|
579
|
+
} catch (error) {
|
|
580
|
+
if (error instanceof ProviderError) throw error;
|
|
581
|
+
throw providerTransportError({ provider: this.config.name, operation: "listModels", cause: error });
|
|
425
582
|
}
|
|
583
|
+
const { status, body } = response;
|
|
426
584
|
if (status !== 200) {
|
|
427
|
-
throw
|
|
585
|
+
throw providerHttpError({
|
|
586
|
+
provider: this.config.name,
|
|
587
|
+
operation: "listModels",
|
|
588
|
+
status,
|
|
589
|
+
body
|
|
590
|
+
});
|
|
428
591
|
}
|
|
592
|
+
return response;
|
|
593
|
+
}
|
|
594
|
+
async listModels() {
|
|
595
|
+
const body = await this.rawModelsResponse();
|
|
429
596
|
const entries = extractModelEntries(body, this.config);
|
|
430
597
|
const models = [];
|
|
431
598
|
let skipped = 0;
|
|
@@ -434,22 +601,44 @@ var OpenAICompatibleProvider = class {
|
|
|
434
601
|
skipped += 1;
|
|
435
602
|
continue;
|
|
436
603
|
}
|
|
437
|
-
models.push(
|
|
438
|
-
provider: this.config.name,
|
|
439
|
-
id: entry.id,
|
|
440
|
-
displayName: entry.id,
|
|
441
|
-
status: "available",
|
|
442
|
-
metadata: {
|
|
443
|
-
...typeof entry.owned_by === "string" ? { ownedBy: entry.owned_by } : {},
|
|
444
|
-
...typeof entry.created === "number" ? { createdAt: entry.created } : {}
|
|
445
|
-
}
|
|
446
|
-
});
|
|
604
|
+
models.push(this.toModelInfo(entry));
|
|
447
605
|
}
|
|
448
606
|
if (skipped > 0) {
|
|
449
607
|
this.log.warn(`Skipped ${skipped} model entr(y/ies) without a valid id.`);
|
|
450
608
|
}
|
|
451
609
|
return models;
|
|
452
610
|
}
|
|
611
|
+
/** Maps a raw model entry onto the normalized metadata shape (§10). */
|
|
612
|
+
toModelInfo(entry) {
|
|
613
|
+
const parameters = Array.isArray(entry.supported_parameters) ? entry.supported_parameters.filter((p) => typeof p === "string") : void 0;
|
|
614
|
+
const contextWindow = typeof entry.context_length === "number" ? entry.context_length : typeof entry.context_window === "number" ? entry.context_window : void 0;
|
|
615
|
+
const inputTypes = normalizeModalities(entry.input_modalities);
|
|
616
|
+
const outputTypes = normalizeModalities(entry.output_modalities);
|
|
617
|
+
return {
|
|
618
|
+
provider: this.config.name,
|
|
619
|
+
id: entry.id,
|
|
620
|
+
displayName: entry.id,
|
|
621
|
+
...typeof entry.name === "string" ? { name: entry.name } : {},
|
|
622
|
+
status: "available",
|
|
623
|
+
...contextWindow !== void 0 ? { contextWindow } : {},
|
|
624
|
+
...inputTypes !== void 0 ? { inputTypes } : {},
|
|
625
|
+
...outputTypes !== void 0 ? { outputTypes } : {},
|
|
626
|
+
...parameters !== void 0 ? {
|
|
627
|
+
supportsTools: parameters.includes("tools") || parameters.includes("tool_choice"),
|
|
628
|
+
supportsStructuredOutput: parameters.includes("response_format"),
|
|
629
|
+
supportsStreaming: parameters.includes("stream")
|
|
630
|
+
} : {},
|
|
631
|
+
metadata: {
|
|
632
|
+
...typeof entry.owned_by === "string" ? { ownedBy: entry.owned_by } : {},
|
|
633
|
+
...typeof entry.created === "number" ? { createdAt: entry.created } : {},
|
|
634
|
+
...parameters !== void 0 ? { supportedParameters: parameters } : {}
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
/** Streaming where the gateway supports it (§11). */
|
|
639
|
+
stream(request, onChunk) {
|
|
640
|
+
return streamChatCompletion(this.config, request, onChunk, this.env);
|
|
641
|
+
}
|
|
453
642
|
async healthCheck() {
|
|
454
643
|
const lastCheckedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
455
644
|
try {
|
|
@@ -479,27 +668,35 @@ var OpenAICompatibleProvider = class {
|
|
|
479
668
|
}
|
|
480
669
|
}
|
|
481
670
|
async chat(request) {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
);
|
|
671
|
+
let response;
|
|
672
|
+
try {
|
|
673
|
+
response = await requestJson(`${this.config.baseUrl}/chat/completions`, {
|
|
674
|
+
method: "POST",
|
|
675
|
+
headers: authHeaders(this.config, this.env),
|
|
676
|
+
timeoutMs: this.requestTimeoutMs,
|
|
677
|
+
body: {
|
|
678
|
+
model: request.model,
|
|
679
|
+
messages: request.messages,
|
|
680
|
+
...request.temperature !== void 0 ? { temperature: request.temperature } : {},
|
|
681
|
+
...request.maxTokens !== void 0 ? { max_tokens: request.maxTokens } : {},
|
|
682
|
+
...request.stop !== void 0 ? { stop: request.stop } : {}
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
} catch (error) {
|
|
686
|
+
if (error instanceof ProviderError) throw error;
|
|
687
|
+
throw providerTransportError({ provider: this.config.name, operation: "chat", cause: error });
|
|
500
688
|
}
|
|
689
|
+
const { status, body } = response;
|
|
501
690
|
if (status !== 200) {
|
|
502
|
-
throw
|
|
691
|
+
throw providerHttpError({
|
|
692
|
+
provider: this.config.name,
|
|
693
|
+
operation: "chat",
|
|
694
|
+
status,
|
|
695
|
+
body,
|
|
696
|
+
...status === 404 ? {
|
|
697
|
+
code: "MODEL_NOT_FOUND"
|
|
698
|
+
} : {}
|
|
699
|
+
});
|
|
503
700
|
}
|
|
504
701
|
const payload = body;
|
|
505
702
|
const choices = Array.isArray(payload.choices) ? payload.choices : [];
|
|
@@ -521,6 +718,81 @@ var OpenAICompatibleProvider = class {
|
|
|
521
718
|
};
|
|
522
719
|
}
|
|
523
720
|
};
|
|
721
|
+
async function streamChatCompletion(config, request, onChunk, env = process.env) {
|
|
722
|
+
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
723
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
724
|
+
let response;
|
|
725
|
+
try {
|
|
726
|
+
response = await fetch(`${baseUrl}/chat/completions`, {
|
|
727
|
+
method: "POST",
|
|
728
|
+
headers: {
|
|
729
|
+
accept: "text/event-stream",
|
|
730
|
+
"content-type": "application/json",
|
|
731
|
+
...authHeaders(config, env)
|
|
732
|
+
},
|
|
733
|
+
body: JSON.stringify({
|
|
734
|
+
model: request.model,
|
|
735
|
+
messages: request.messages,
|
|
736
|
+
stream: true,
|
|
737
|
+
...request.temperature !== void 0 ? { temperature: request.temperature } : {},
|
|
738
|
+
...request.maxTokens !== void 0 ? { max_tokens: request.maxTokens } : {}
|
|
739
|
+
}),
|
|
740
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
741
|
+
});
|
|
742
|
+
} catch (error) {
|
|
743
|
+
throw providerTransportError({ provider: config.name, operation: "stream", cause: error });
|
|
744
|
+
}
|
|
745
|
+
if (!response.ok) {
|
|
746
|
+
const body = await response.json().catch(() => void 0);
|
|
747
|
+
throw providerHttpError({ provider: config.name, operation: "stream", status: response.status, body });
|
|
748
|
+
}
|
|
749
|
+
if (!response.body) {
|
|
750
|
+
throw providerHttpError({
|
|
751
|
+
provider: config.name,
|
|
752
|
+
operation: "stream",
|
|
753
|
+
status: 500,
|
|
754
|
+
body: { error: { message: "streaming is not supported by this endpoint" } }
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
let content = "";
|
|
758
|
+
let model = request.model;
|
|
759
|
+
let finishReason;
|
|
760
|
+
const decoder = new TextDecoder();
|
|
761
|
+
let buffer = "";
|
|
762
|
+
for await (const piece of response.body) {
|
|
763
|
+
buffer += decoder.decode(piece, { stream: true });
|
|
764
|
+
let newline = buffer.indexOf("\n");
|
|
765
|
+
while (newline >= 0) {
|
|
766
|
+
const line = buffer.slice(0, newline).trim();
|
|
767
|
+
buffer = buffer.slice(newline + 1);
|
|
768
|
+
newline = buffer.indexOf("\n");
|
|
769
|
+
if (line.length === 0 || line.startsWith(":")) continue;
|
|
770
|
+
const data = line.startsWith("data:") ? line.slice(5).trim() : line;
|
|
771
|
+
if (data === "[DONE]") continue;
|
|
772
|
+
let event;
|
|
773
|
+
try {
|
|
774
|
+
event = JSON.parse(data);
|
|
775
|
+
} catch {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (typeof event.model === "string") model = event.model;
|
|
779
|
+
const choices = Array.isArray(event.choices) ? event.choices : [];
|
|
780
|
+
const first = choices[0];
|
|
781
|
+
const delta = typeof first?.delta?.content === "string" ? first.delta.content : "";
|
|
782
|
+
if (delta.length > 0) content += delta;
|
|
783
|
+
if (typeof first?.finish_reason === "string") finishReason = first.finish_reason;
|
|
784
|
+
onChunk({ delta, ...finishReason !== void 0 ? { finishReason } : {}, raw: event });
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return { model, content, ...finishReason !== void 0 ? { finishReason } : {} };
|
|
788
|
+
}
|
|
789
|
+
function normalizeModalities(value) {
|
|
790
|
+
const list = Array.isArray(value) ? value : typeof value === "string" ? [value] : void 0;
|
|
791
|
+
if (!list) return void 0;
|
|
792
|
+
const allowed = /* @__PURE__ */ new Set(["text", "image", "audio", "video", "embedding"]);
|
|
793
|
+
const modalities = list.filter((m) => typeof m === "string" && allowed.has(m));
|
|
794
|
+
return modalities.length > 0 ? modalities : void 0;
|
|
795
|
+
}
|
|
524
796
|
function normalizeBaseUrl(baseUrl) {
|
|
525
797
|
return baseUrl.replace(/\/+$/, "");
|
|
526
798
|
}
|
|
@@ -535,14 +807,6 @@ function extractModelEntries(body, config) {
|
|
|
535
807
|
{ details: { endpoint: "models" } }
|
|
536
808
|
);
|
|
537
809
|
}
|
|
538
|
-
function authFailed(config, status) {
|
|
539
|
-
const envHint = config.apiKeyEnvVar ? ` Check that $${config.apiKeyEnvVar} is set and valid.` : " The provider requires authentication; configure api_key_env_var for it.";
|
|
540
|
-
return new ProviderError(
|
|
541
|
-
"PROVIDER_AUTH_FAILED",
|
|
542
|
-
`Authentication with provider "${config.name}" failed (HTTP ${status}).${envHint}`,
|
|
543
|
-
{ details: { status } }
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
810
|
function unavailable(config, message, status) {
|
|
547
811
|
return new ProviderError("PROVIDER_UNAVAILABLE", `Provider "${config.name}": ${message}`, {
|
|
548
812
|
details: { status }
|
|
@@ -555,11 +819,33 @@ function inferHealth(error) {
|
|
|
555
819
|
|
|
556
820
|
// src/providers/omnihilbras/index.ts
|
|
557
821
|
var OmniHilbrasProvider = class extends OpenAICompatibleProvider {
|
|
822
|
+
gateway;
|
|
823
|
+
async listModels() {
|
|
824
|
+
const raw = await this.rawModelsResponse();
|
|
825
|
+
await this.extractGatewayMetadata(raw);
|
|
826
|
+
const models = await super.listModels();
|
|
827
|
+
return this.gateway ? models.map((model) => ({ ...model, metadata: { ...model.metadata, gateway: this.gateway } })) : models;
|
|
828
|
+
}
|
|
558
829
|
/**
|
|
559
830
|
* Run the full connect flow. Throws on authentication or transport failure
|
|
560
831
|
* (use healthCheck() for a non-throwing probe).
|
|
561
832
|
*/
|
|
562
|
-
|
|
833
|
+
/**
|
|
834
|
+
* Gateway metadata reported alongside the model list (gateway version, tier,
|
|
835
|
+
* region…), kept separate from per-model metadata (§11 — provider metadata).
|
|
836
|
+
*/
|
|
837
|
+
gatewayMetadata() {
|
|
838
|
+
return this.gateway;
|
|
839
|
+
}
|
|
840
|
+
async extractGatewayMetadata(body) {
|
|
841
|
+
if (body !== null && typeof body === "object") {
|
|
842
|
+
const gateway = body.gateway;
|
|
843
|
+
if (gateway !== null && typeof gateway === "object") {
|
|
844
|
+
this.gateway = gateway;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
async connectAndRegister(registry) {
|
|
563
849
|
const apiKey = resolveApiKey(this.config);
|
|
564
850
|
const discovered = await this.listModels();
|
|
565
851
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -664,19 +950,431 @@ var ModelRegistry = class {
|
|
|
664
950
|
|
|
665
951
|
// src/agents/process/index.ts
|
|
666
952
|
import { spawn } from "child_process";
|
|
667
|
-
|
|
668
|
-
|
|
953
|
+
|
|
954
|
+
// src/agent-protocol/version.ts
|
|
955
|
+
var PROTOCOL_V2 = "omninode-agent-protocol/2";
|
|
956
|
+
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
957
|
+
"omninode-agent-protocol/1",
|
|
958
|
+
PROTOCOL_V2
|
|
959
|
+
];
|
|
960
|
+
var MAX_MESSAGE_BYTES = 1048576;
|
|
961
|
+
function isSupportedProtocol(protocol) {
|
|
962
|
+
return SUPPORTED_PROTOCOL_VERSIONS.includes(protocol);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// src/agent-protocol/types.ts
|
|
966
|
+
function newMessageId(prefix = "msg") {
|
|
967
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
968
|
+
return `${prefix}-${Date.now().toString(36)}-${random}`;
|
|
969
|
+
}
|
|
970
|
+
function envelope(type, payload, ids = {}) {
|
|
971
|
+
return {
|
|
972
|
+
protocol: PROTOCOL_V2,
|
|
973
|
+
messageId: newMessageId(),
|
|
974
|
+
...ids.requestId !== void 0 ? { requestId: ids.requestId } : {},
|
|
975
|
+
...ids.taskId !== void 0 ? { taskId: ids.taskId } : {},
|
|
976
|
+
...ids.agentId !== void 0 ? { agentId: ids.agentId } : {},
|
|
977
|
+
...ids.pipelineId !== void 0 ? { pipelineId: ids.pipelineId } : {},
|
|
978
|
+
...ids.executionId !== void 0 ? { executionId: ids.executionId } : {},
|
|
979
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
980
|
+
type,
|
|
981
|
+
payload,
|
|
982
|
+
...ids.metadata !== void 0 ? { metadata: ids.metadata } : {}
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/agent-protocol/codec.ts
|
|
987
|
+
var KNOWN_TYPES = /* @__PURE__ */ new Set([
|
|
988
|
+
// node → agent
|
|
989
|
+
"TASK",
|
|
990
|
+
"HELLO_ACK",
|
|
991
|
+
"CONTEXT",
|
|
992
|
+
"INSTRUCTION",
|
|
993
|
+
"RESPONSE",
|
|
994
|
+
"CANCEL",
|
|
995
|
+
// agent → node
|
|
996
|
+
"HELLO",
|
|
997
|
+
"TASK_REQUEST",
|
|
998
|
+
"TASK_ACCEPTED",
|
|
999
|
+
"TASK_STARTED",
|
|
1000
|
+
"STATUS",
|
|
1001
|
+
"QUESTION",
|
|
1002
|
+
"REPORT",
|
|
1003
|
+
"ARTIFACT",
|
|
1004
|
+
"COMPLETION",
|
|
1005
|
+
"ERROR",
|
|
1006
|
+
"CANCELLED"
|
|
1007
|
+
]);
|
|
1008
|
+
var PAYLOAD_REQUIRED_TYPES = /* @__PURE__ */ new Set(["TASK", "REPORT", "COMPLETION", "ERROR", "QUESTION", "ARTIFACT", "HELLO"]);
|
|
1009
|
+
function validateEnvelope(value, line) {
|
|
1010
|
+
const sample = line?.slice(0, 200);
|
|
1011
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
1012
|
+
return { ok: false, error: { code: "NOT_AN_OBJECT", message: "message must be a JSON object", ...sample ? { sample } : {} } };
|
|
1013
|
+
}
|
|
1014
|
+
const raw = value;
|
|
1015
|
+
const protocol = raw.protocol;
|
|
1016
|
+
if (protocol === void 0) {
|
|
1017
|
+
const type = raw.type;
|
|
1018
|
+
if (typeof type !== "string" || !KNOWN_TYPES.has(type)) {
|
|
1019
|
+
return { ok: false, error: { code: "UNKNOWN_TYPE", message: `unknown message type ${String(type)}`, ...sample ? { sample } : {} } };
|
|
1020
|
+
}
|
|
1021
|
+
return {
|
|
1022
|
+
ok: true,
|
|
1023
|
+
legacy: true,
|
|
1024
|
+
envelope: {
|
|
1025
|
+
protocol: "omninode-agent-protocol/1",
|
|
1026
|
+
messageId: typeof raw.messageId === "string" ? raw.messageId : "legacy",
|
|
1027
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1028
|
+
type,
|
|
1029
|
+
payload: raw.payload
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
if (typeof protocol !== "string" || !isSupportedProtocol(protocol)) {
|
|
1034
|
+
return {
|
|
1035
|
+
ok: false,
|
|
1036
|
+
error: { code: "UNSUPPORTED_VERSION", message: `unsupported protocol "${String(protocol)}"`, ...sample ? { sample } : {} }
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
if (raw.type === void 0) {
|
|
1040
|
+
return { ok: false, error: { code: "MISSING_FIELD", message: "message is missing 'type'", ...sample ? { sample } : {} } };
|
|
1041
|
+
}
|
|
1042
|
+
if (typeof raw.type !== "string" || !KNOWN_TYPES.has(raw.type)) {
|
|
1043
|
+
return { ok: false, error: { code: "UNKNOWN_TYPE", message: `unknown message type ${String(raw.type)}`, ...sample ? { sample } : {} } };
|
|
1044
|
+
}
|
|
1045
|
+
if (raw.timestamp !== void 0 && typeof raw.timestamp !== "string") {
|
|
1046
|
+
return { ok: false, error: { code: "MISSING_FIELD", message: "'timestamp' must be a string", ...sample ? { sample } : {} } };
|
|
1047
|
+
}
|
|
1048
|
+
if (PAYLOAD_REQUIRED_TYPES.has(raw.type) && (raw.payload === void 0 || raw.payload === null)) {
|
|
1049
|
+
return { ok: false, error: { code: "INVALID_PAYLOAD", message: `${raw.type} requires a payload`, ...sample ? { sample } : {} } };
|
|
1050
|
+
}
|
|
1051
|
+
return {
|
|
1052
|
+
ok: true,
|
|
1053
|
+
legacy: protocol !== PROTOCOL_V2,
|
|
1054
|
+
envelope: {
|
|
1055
|
+
protocol,
|
|
1056
|
+
messageId: typeof raw.messageId === "string" ? raw.messageId : "unknown",
|
|
1057
|
+
...typeof raw.requestId === "string" ? { requestId: raw.requestId } : {},
|
|
1058
|
+
...typeof raw.taskId === "string" ? { taskId: raw.taskId } : {},
|
|
1059
|
+
...typeof raw.agentId === "string" ? { agentId: raw.agentId } : {},
|
|
1060
|
+
...typeof raw.pipelineId === "string" ? { pipelineId: raw.pipelineId } : {},
|
|
1061
|
+
...typeof raw.executionId === "string" ? { executionId: raw.executionId } : {},
|
|
1062
|
+
timestamp: typeof raw.timestamp === "string" ? raw.timestamp : (/* @__PURE__ */ new Date()).toISOString(),
|
|
1063
|
+
type: raw.type,
|
|
1064
|
+
payload: raw.payload,
|
|
1065
|
+
...raw.metadata !== null && typeof raw.metadata === "object" ? { metadata: raw.metadata } : {}
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
function decodeMessage(line, maxBytes = MAX_MESSAGE_BYTES) {
|
|
1070
|
+
const trimmed = line.trim();
|
|
1071
|
+
if (trimmed.length === 0) {
|
|
1072
|
+
return { ok: false, error: { code: "INVALID_JSON", message: "empty line" } };
|
|
1073
|
+
}
|
|
1074
|
+
if (trimmed.length > maxBytes) {
|
|
1075
|
+
return {
|
|
1076
|
+
ok: false,
|
|
1077
|
+
error: { code: "OVERSIZED", message: `message of ${trimmed.length} bytes exceeds the ${maxBytes} byte limit`, sample: trimmed.slice(0, 200) }
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
let value;
|
|
1081
|
+
try {
|
|
1082
|
+
value = JSON.parse(trimmed);
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
return {
|
|
1085
|
+
ok: false,
|
|
1086
|
+
error: { code: "INVALID_JSON", message: error instanceof Error ? error.message : "invalid JSON", sample: trimmed.slice(0, 200) }
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
return validateEnvelope(value, trimmed);
|
|
1090
|
+
}
|
|
1091
|
+
function encodeMessage(envelope2, maxBytes = MAX_MESSAGE_BYTES) {
|
|
1092
|
+
const line = JSON.stringify(envelope2);
|
|
1093
|
+
if (line.length > maxBytes) {
|
|
1094
|
+
throw new Error(`outbound protocol message of ${line.length} bytes exceeds the ${maxBytes} byte limit`);
|
|
1095
|
+
}
|
|
1096
|
+
return line;
|
|
1097
|
+
}
|
|
1098
|
+
var ProtocolDecoder = class {
|
|
1099
|
+
buffer = "";
|
|
1100
|
+
maxBytes;
|
|
1101
|
+
constructor(maxBytes = MAX_MESSAGE_BYTES) {
|
|
1102
|
+
this.maxBytes = maxBytes;
|
|
1103
|
+
}
|
|
1104
|
+
push(chunk) {
|
|
1105
|
+
this.buffer += chunk;
|
|
1106
|
+
const results = [];
|
|
1107
|
+
let index = this.buffer.indexOf("\n");
|
|
1108
|
+
while (index >= 0) {
|
|
1109
|
+
const line = this.buffer.slice(0, index);
|
|
1110
|
+
this.buffer = this.buffer.slice(index + 1);
|
|
1111
|
+
if (line.trim().length > 0) results.push(decodeMessage(line, this.maxBytes));
|
|
1112
|
+
index = this.buffer.indexOf("\n");
|
|
1113
|
+
}
|
|
1114
|
+
if (this.buffer.length > this.maxBytes) {
|
|
1115
|
+
results.push({
|
|
1116
|
+
ok: false,
|
|
1117
|
+
error: { code: "OVERSIZED", message: `line exceeded ${this.maxBytes} bytes without a newline`, sample: this.buffer.slice(0, 200) }
|
|
1118
|
+
});
|
|
1119
|
+
this.buffer = "";
|
|
1120
|
+
}
|
|
1121
|
+
return results;
|
|
1122
|
+
}
|
|
1123
|
+
/** Flush any trailing line when the process exits. */
|
|
1124
|
+
flush() {
|
|
1125
|
+
if (this.buffer.trim().length === 0) {
|
|
1126
|
+
this.buffer = "";
|
|
1127
|
+
return [];
|
|
1128
|
+
}
|
|
1129
|
+
const result = decodeMessage(this.buffer, this.maxBytes);
|
|
1130
|
+
this.buffer = "";
|
|
1131
|
+
return [result];
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
// src/agent-protocol/handshake.ts
|
|
1136
|
+
function parseHello(envelopeValue) {
|
|
1137
|
+
if (envelopeValue.type !== "HELLO") {
|
|
1138
|
+
return { ok: false, reason: `expected HELLO, received ${envelopeValue.type}` };
|
|
1139
|
+
}
|
|
1140
|
+
const payload = envelopeValue.payload;
|
|
1141
|
+
if (payload === null || typeof payload !== "object") {
|
|
1142
|
+
return { ok: false, reason: "HELLO payload must be an object" };
|
|
1143
|
+
}
|
|
1144
|
+
const p = payload;
|
|
1145
|
+
if (typeof p.name !== "string" || p.name.length === 0) {
|
|
1146
|
+
return { ok: false, reason: "HELLO payload is missing 'name'" };
|
|
1147
|
+
}
|
|
1148
|
+
return {
|
|
1149
|
+
ok: true,
|
|
1150
|
+
descriptor: {
|
|
1151
|
+
protocol: envelopeValue.protocol,
|
|
1152
|
+
name: p.name,
|
|
1153
|
+
...typeof p.version === "string" ? { version: p.version } : {},
|
|
1154
|
+
capabilities: Array.isArray(p.capabilities) ? p.capabilities.filter((c) => typeof c === "string") : [],
|
|
1155
|
+
supports: Array.isArray(p.supports) ? p.supports.filter((s) => typeof s === "string") : []
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
function buildHello(descriptor) {
|
|
1160
|
+
return envelope("HELLO", descriptor, { agentId: descriptor.name });
|
|
1161
|
+
}
|
|
1162
|
+
function buildHelloAck(descriptor) {
|
|
1163
|
+
return envelope("HELLO_ACK", {
|
|
1164
|
+
ok: true,
|
|
1165
|
+
protocol: PROTOCOL_V2,
|
|
1166
|
+
agent: descriptor.name,
|
|
1167
|
+
...descriptor.version !== void 0 ? { agentVersion: descriptor.version } : {},
|
|
1168
|
+
supportedProtocols: [PROTOCOL_V2]
|
|
1169
|
+
}, { agentId: descriptor.name });
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// src/agent-protocol/session.ts
|
|
1173
|
+
var ProtocolSession = class {
|
|
1174
|
+
decoder;
|
|
1175
|
+
ids;
|
|
1176
|
+
responder;
|
|
1177
|
+
violations = [];
|
|
1178
|
+
questions = [];
|
|
1179
|
+
artifacts = [];
|
|
1180
|
+
reports = [];
|
|
1181
|
+
descriptor;
|
|
1182
|
+
completionSummary;
|
|
1183
|
+
errorMessage;
|
|
1184
|
+
constructor(ids, options = {}) {
|
|
1185
|
+
this.ids = ids;
|
|
1186
|
+
this.responder = options.responder;
|
|
1187
|
+
this.decoder = new ProtocolDecoder(options.maxBytes);
|
|
1188
|
+
}
|
|
1189
|
+
/** The v2 messages OmniNode sends to open a task (written to the agent's stdin). */
|
|
1190
|
+
taskMessages(objective, role, context, instruction) {
|
|
1191
|
+
const ids = this.ids;
|
|
1192
|
+
const messages = [envelope("TASK", { taskId: ids.taskId, objective }, ids)];
|
|
1193
|
+
if (role !== void 0) messages.push(envelope("CONTEXT", { role }, ids));
|
|
1194
|
+
if (context !== void 0 && context.length > 0) messages.push(envelope("CONTEXT", { body: context }, ids));
|
|
1195
|
+
if (instruction !== void 0 && instruction.length > 0) messages.push(envelope("INSTRUCTION", { text: instruction }, ids));
|
|
1196
|
+
return messages.map((m) => encodeMessage(m));
|
|
1197
|
+
}
|
|
1198
|
+
helloAck(descriptor) {
|
|
1199
|
+
return encodeMessage(buildHelloAck(descriptor));
|
|
1200
|
+
}
|
|
1201
|
+
/** Consumes decoded stdout, answering questions as they arrive. */
|
|
1202
|
+
async handle(decoded) {
|
|
1203
|
+
if (!decoded.ok) {
|
|
1204
|
+
this.violations.push(decoded.error);
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
const message = decoded.envelope;
|
|
1208
|
+
if (message.protocol === PROTOCOL_V2) this.sawV2 = true;
|
|
1209
|
+
if (message.type === "HELLO") {
|
|
1210
|
+
const hello = parseHello(message);
|
|
1211
|
+
if (hello.ok) this.descriptor = hello.descriptor;
|
|
1212
|
+
else this.violations.push({ code: "INVALID_PAYLOAD", message: `bad HELLO: ${hello.reason}` });
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
if (message.type === "QUESTION") {
|
|
1216
|
+
await this.answerQuestion(message);
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
if (message.type === "REPORT") {
|
|
1220
|
+
this.reports.push(message.payload);
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
if (message.type === "ARTIFACT") {
|
|
1224
|
+
this.artifacts.push(message.payload);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
if (message.type === "COMPLETION") {
|
|
1228
|
+
const payload = message.payload;
|
|
1229
|
+
this.completionSummary = typeof payload?.summary === "string" ? payload.summary : "";
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (message.type === "ERROR") {
|
|
1233
|
+
const payload = message.payload;
|
|
1234
|
+
const detail = typeof payload?.message === "string" ? payload.message : "agent reported an error";
|
|
1235
|
+
const code = typeof payload?.code === "string" ? ` [${payload.code}]` : "";
|
|
1236
|
+
this.errorMessage = `Agent error${code}: ${detail}`;
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
if (message.type === "TASK_ACCEPTED" || message.type === "TASK_STARTED" || message.type === "STATUS") {
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
this.violations.push({
|
|
1243
|
+
code: "UNKNOWN_TYPE",
|
|
1244
|
+
message: `unexpected message type ${message.type} from agent`
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
async answerQuestion(message) {
|
|
1248
|
+
const payload = message.payload ?? {};
|
|
1249
|
+
const questionId = typeof payload.questionId === "string" ? payload.questionId : message.messageId;
|
|
1250
|
+
const question = typeof payload.question === "string" ? payload.question : "";
|
|
1251
|
+
const expected = typeof payload.expected === "string" ? payload.expected : "text";
|
|
1252
|
+
let answer;
|
|
1253
|
+
if (this.responder) {
|
|
1254
|
+
try {
|
|
1255
|
+
answer = await this.responder({ questionId, question, expected });
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
this.violations.push({
|
|
1258
|
+
code: "INVALID_PAYLOAD",
|
|
1259
|
+
message: `question responder failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
this.questions.push({ questionId, question, ...answer !== void 0 ? { answer } : {} });
|
|
1264
|
+
this.pendingAnswers.push(
|
|
1265
|
+
encodeMessage(
|
|
1266
|
+
envelope("RESPONSE", { questionId, ...answer !== void 0 ? { body: answer } : {} }, {
|
|
1267
|
+
...this.ids,
|
|
1268
|
+
requestId: message.requestId ?? this.ids.requestId
|
|
1269
|
+
})
|
|
1270
|
+
)
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
pendingAnswers = [];
|
|
1274
|
+
/** True once any inbound message carried the v2 protocol field. */
|
|
1275
|
+
sawV2 = false;
|
|
1276
|
+
};
|
|
1277
|
+
async function decodeStream(session, chunk) {
|
|
1278
|
+
const decoded = session.decoder.push(chunk);
|
|
1279
|
+
for (const result of decoded) {
|
|
1280
|
+
await session.handle(result);
|
|
1281
|
+
}
|
|
1282
|
+
return session.pendingAnswers.splice(0);
|
|
1283
|
+
}
|
|
1284
|
+
function requireCompletion(session) {
|
|
1285
|
+
if (session.errorMessage !== void 0) {
|
|
1286
|
+
throw new ProtocolError(session.errorMessage);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// src/agents/cwd.ts
|
|
1291
|
+
import { statSync } from "fs";
|
|
1292
|
+
import { isAbsolute, relative, resolve } from "path";
|
|
1293
|
+
function validateWorkingDirectory(cwd, options = {}) {
|
|
1294
|
+
if (!cwd) return void 0;
|
|
1295
|
+
const normalized = isAbsolute(cwd) ? resolve(cwd) : resolve(process.cwd(), cwd);
|
|
1296
|
+
let stats;
|
|
1297
|
+
try {
|
|
1298
|
+
stats = statSync(normalized);
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
throw new AgentError(
|
|
1301
|
+
"AGENT_NOT_FOUND",
|
|
1302
|
+
`Working directory "${normalized}" does not exist or is not accessible.`,
|
|
1303
|
+
{ cause: error, details: { cwd: normalized } }
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
if (!stats.isDirectory()) {
|
|
1307
|
+
throw new AgentError("AGENT_NOT_FOUND", `Working directory "${normalized}" is not a directory.`, {
|
|
1308
|
+
details: { cwd: normalized }
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
try {
|
|
1312
|
+
statSync(resolve(normalized, "."));
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
throw new AgentError(
|
|
1315
|
+
"AGENT_NOT_FOUND",
|
|
1316
|
+
`Working directory "${normalized}" is not accessible.`,
|
|
1317
|
+
{ cause: error }
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
1321
|
+
const rel = relative(projectRoot, normalized);
|
|
1322
|
+
const outsideProject = rel.startsWith("..") || isAbsolute(rel);
|
|
1323
|
+
if (outsideProject && options.allowExternal !== true) {
|
|
1324
|
+
throw new AgentError(
|
|
1325
|
+
"AGENT_NOT_FOUND",
|
|
1326
|
+
`Working directory "${normalized}" is outside the project root "${projectRoot}". Set allow_external_cwd: true on the agent to permit this.`,
|
|
1327
|
+
{ details: { cwd: normalized, projectRoot } }
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
return { cwd: normalized, outsideProject };
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/agents/env.ts
|
|
1334
|
+
var BASELINE_KEYS = ["PATH", "HOME"];
|
|
1335
|
+
function resolveEnvPolicy(config) {
|
|
1336
|
+
if (config.envPolicy !== void 0) return config.envPolicy;
|
|
1337
|
+
if (config.inheritEnv === false) return "explicit";
|
|
1338
|
+
return "inherit";
|
|
1339
|
+
}
|
|
669
1340
|
function buildChildEnv(config) {
|
|
670
1341
|
const configured = config.env ?? {};
|
|
671
|
-
|
|
1342
|
+
const policy = resolveEnvPolicy(config);
|
|
1343
|
+
if (policy === "inherit") {
|
|
672
1344
|
return { ...process.env, ...configured };
|
|
673
1345
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
1346
|
+
const base = {};
|
|
1347
|
+
for (const key of BASELINE_KEYS) {
|
|
1348
|
+
const value = process.env[key];
|
|
1349
|
+
if (value !== void 0) base[key] = value;
|
|
1350
|
+
}
|
|
1351
|
+
switch (policy) {
|
|
1352
|
+
case "explicit":
|
|
1353
|
+
return { ...base, ...configured };
|
|
1354
|
+
case "allowlist": {
|
|
1355
|
+
const allowed = { ...base };
|
|
1356
|
+
for (const key of config.envAllowlist ?? []) {
|
|
1357
|
+
const value = process.env[key];
|
|
1358
|
+
if (value !== void 0) allowed[key] = value;
|
|
1359
|
+
}
|
|
1360
|
+
return { ...allowed, ...configured };
|
|
1361
|
+
}
|
|
1362
|
+
case "denylist": {
|
|
1363
|
+
const denied = new Set((config.envDenylist ?? []).map((k) => k.toUpperCase()));
|
|
1364
|
+
const filtered = {};
|
|
1365
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1366
|
+
if (denied.has(key.toUpperCase())) continue;
|
|
1367
|
+
filtered[key] = value;
|
|
1368
|
+
}
|
|
1369
|
+
return { ...filtered, ...configured };
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
679
1372
|
}
|
|
1373
|
+
|
|
1374
|
+
// src/agents/process/index.ts
|
|
1375
|
+
var DEFAULT_TIMEOUT_MS2 = 6e5;
|
|
1376
|
+
var KILL_GRACE_MS = 5e3;
|
|
1377
|
+
var DEFAULT_MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
680
1378
|
function composeTaskText(input) {
|
|
681
1379
|
const sections = [`# Objective
|
|
682
1380
|
${input.objective}`];
|
|
@@ -697,18 +1395,19 @@ ${input.instruction}`);
|
|
|
697
1395
|
return sections.join("\n\n");
|
|
698
1396
|
}
|
|
699
1397
|
function buildProtocolMessages(input, agentName) {
|
|
700
|
-
const
|
|
1398
|
+
const session = new ProtocolSession({
|
|
1399
|
+
requestId: `req-${input.taskId}`,
|
|
701
1400
|
taskId: input.taskId,
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1401
|
+
agentId: agentName,
|
|
1402
|
+
...input.pipelineId !== void 0 ? { pipelineId: input.pipelineId } : {},
|
|
1403
|
+
...input.executionId !== void 0 ? { executionId: input.executionId } : {}
|
|
1404
|
+
});
|
|
1405
|
+
return session.taskMessages(
|
|
1406
|
+
input.objective,
|
|
1407
|
+
input.role,
|
|
1408
|
+
input.context,
|
|
1409
|
+
input.instruction
|
|
1410
|
+
);
|
|
712
1411
|
}
|
|
713
1412
|
var ProcessAgent = class {
|
|
714
1413
|
config;
|
|
@@ -737,40 +1436,73 @@ var ProcessAgent = class {
|
|
|
737
1436
|
}
|
|
738
1437
|
const mode = this.config.inputMode ?? "stdin";
|
|
739
1438
|
const timeoutMs = this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
740
|
-
return new Promise((
|
|
1439
|
+
return new Promise((resolve2, reject) => {
|
|
1440
|
+
let cwd;
|
|
1441
|
+
try {
|
|
1442
|
+
cwd = validateWorkingDirectory(this.config.cwd, {
|
|
1443
|
+
allowExternal: this.config.allowExternalCwd
|
|
1444
|
+
})?.cwd;
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
reject(error);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
741
1449
|
const child = spawn(
|
|
742
1450
|
command,
|
|
743
1451
|
[...this.config.args ?? [], ...mode === "arg" ? [composeTaskText(input)] : []],
|
|
744
1452
|
{
|
|
745
|
-
cwd:
|
|
1453
|
+
...cwd !== void 0 ? { cwd } : {},
|
|
746
1454
|
env: buildChildEnv(this.config),
|
|
747
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1455
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1456
|
+
// Own process group so the whole tree can be terminated together
|
|
1457
|
+
// (roadmap §8: no orphaned children).
|
|
1458
|
+
...process.platform === "win32" ? {} : { detached: true }
|
|
748
1459
|
}
|
|
749
1460
|
);
|
|
750
1461
|
this.running.set(input.taskId, child);
|
|
1462
|
+
GLOBAL_RUNNING.add(child);
|
|
1463
|
+
installSignalForwarding();
|
|
751
1464
|
let stdout = "";
|
|
752
1465
|
let stderr = "";
|
|
753
1466
|
let timedOut = false;
|
|
1467
|
+
let outputLimitExceeded = false;
|
|
754
1468
|
let settled = false;
|
|
1469
|
+
const maxOutputBytes = this.config.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
755
1470
|
const timer = setTimeout(() => {
|
|
756
1471
|
timedOut = true;
|
|
757
|
-
this.log.warn(`Task ${input.taskId} exceeded ${timeoutMs}ms \u2014 killing process.`);
|
|
758
|
-
child
|
|
759
|
-
setTimeout(() => child
|
|
1472
|
+
this.log.warn(`Task ${input.taskId} exceeded ${timeoutMs}ms \u2014 killing process tree.`);
|
|
1473
|
+
killTree(child, "SIGTERM");
|
|
1474
|
+
setTimeout(() => killTree(child, "SIGKILL"), KILL_GRACE_MS).unref();
|
|
760
1475
|
}, timeoutMs);
|
|
761
1476
|
const settle = (finish) => {
|
|
762
1477
|
if (settled) return;
|
|
763
1478
|
settled = true;
|
|
764
1479
|
clearTimeout(timer);
|
|
765
1480
|
this.running.delete(input.taskId);
|
|
1481
|
+
GLOBAL_RUNNING.delete(child);
|
|
766
1482
|
finish();
|
|
767
1483
|
};
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1484
|
+
const collect = (into, chunk) => {
|
|
1485
|
+
if (outputLimitExceeded) return;
|
|
1486
|
+
const text = sanitizeChunk(chunk);
|
|
1487
|
+
if (into === "stdout") {
|
|
1488
|
+
if (stdout.length + text.length > maxOutputBytes) {
|
|
1489
|
+
stdout = (stdout + text).slice(0, maxOutputBytes);
|
|
1490
|
+
outputLimitExceeded = true;
|
|
1491
|
+
this.log.warn(`Agent "${this.config.name}" exceeded the ${maxOutputBytes} byte output limit \u2014 killing it.`);
|
|
1492
|
+
killTree(child, "SIGKILL");
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
stdout += text;
|
|
1496
|
+
} else {
|
|
1497
|
+
if (stderr.length + text.length > maxOutputBytes) {
|
|
1498
|
+
stderr = (stderr + text).slice(0, maxOutputBytes);
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
stderr += text;
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
child.stdout?.on("data", (chunk) => collect("stdout", chunk));
|
|
1505
|
+
child.stderr?.on("data", (chunk) => collect("stderr", chunk));
|
|
774
1506
|
child.stdin?.on("error", () => {
|
|
775
1507
|
});
|
|
776
1508
|
if (mode !== "arg") {
|
|
@@ -804,7 +1536,19 @@ var ProcessAgent = class {
|
|
|
804
1536
|
new AgentError(
|
|
805
1537
|
"AGENT_TIMEOUT",
|
|
806
1538
|
`Agent "${this.config.name}" timed out after ${timeoutMs}ms and was killed.`,
|
|
807
|
-
{
|
|
1539
|
+
{
|
|
1540
|
+
details: {
|
|
1541
|
+
operation: "agent.run",
|
|
1542
|
+
agent: this.config.name,
|
|
1543
|
+
durationMs: timeoutMs,
|
|
1544
|
+
// The process was started and may have performed side
|
|
1545
|
+
// effects before being killed — the outcome is unknown,
|
|
1546
|
+
// not failed (roadmap §6.3).
|
|
1547
|
+
dispatched: true,
|
|
1548
|
+
stdout: stdout.slice(0, 4e3),
|
|
1549
|
+
stderr: stderr.slice(0, 2e3)
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
808
1552
|
)
|
|
809
1553
|
);
|
|
810
1554
|
return;
|
|
@@ -812,7 +1556,7 @@ var ProcessAgent = class {
|
|
|
812
1556
|
const logs = stderr.trim().length > 0 ? stderr.split("\n") : void 0;
|
|
813
1557
|
if (mode === "protocol") {
|
|
814
1558
|
try {
|
|
815
|
-
|
|
1559
|
+
resolve2(
|
|
816
1560
|
interpretProtocolRun({
|
|
817
1561
|
input,
|
|
818
1562
|
agentName: this.config.name,
|
|
@@ -827,14 +1571,14 @@ var ProcessAgent = class {
|
|
|
827
1571
|
return;
|
|
828
1572
|
}
|
|
829
1573
|
const trimmed = stdout.trim();
|
|
830
|
-
|
|
1574
|
+
resolve2({
|
|
831
1575
|
taskId: input.taskId,
|
|
832
|
-
status: code === 0 ? "completed" : "failed",
|
|
1576
|
+
status: code === 0 && !outputLimitExceeded ? "completed" : "failed",
|
|
833
1577
|
summary: trimmed.length > 0 ? firstLine(trimmed) : void 0,
|
|
834
1578
|
rawOutput: trimmed.length > 0 ? trimmed : void 0,
|
|
835
1579
|
exitCode: code ?? void 0,
|
|
836
1580
|
logs,
|
|
837
|
-
error:
|
|
1581
|
+
...outputLimitExceeded ? { error: `Agent exceeded the ${maxOutputBytes} byte output limit and was terminated.` } : code === 0 ? {} : { error: describeExit(code, signal) }
|
|
838
1582
|
});
|
|
839
1583
|
});
|
|
840
1584
|
});
|
|
@@ -843,67 +1587,117 @@ var ProcessAgent = class {
|
|
|
843
1587
|
/** Ask the agent to stop current work (SIGTERM). */
|
|
844
1588
|
async cancel(taskId) {
|
|
845
1589
|
const child = this.running.get(taskId);
|
|
846
|
-
if (child) child
|
|
1590
|
+
if (child) killTree(child, "SIGTERM");
|
|
847
1591
|
}
|
|
848
1592
|
runningTaskIds() {
|
|
849
1593
|
return [...this.running.keys()];
|
|
850
1594
|
}
|
|
851
1595
|
};
|
|
852
|
-
function interpretProtocolRun({
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
const code = typeof payload?.code === "string" ? ` [${payload.code}]` : "";
|
|
873
|
-
error = `Agent error${code}: ${detail}`;
|
|
874
|
-
}
|
|
1596
|
+
function interpretProtocolRun({
|
|
1597
|
+
input,
|
|
1598
|
+
agentName,
|
|
1599
|
+
stdout,
|
|
1600
|
+
exitCode,
|
|
1601
|
+
logs,
|
|
1602
|
+
responder
|
|
1603
|
+
}) {
|
|
1604
|
+
const session = new ProtocolSession(
|
|
1605
|
+
{
|
|
1606
|
+
requestId: `req-${input.taskId}`,
|
|
1607
|
+
taskId: input.taskId,
|
|
1608
|
+
agentId: agentName,
|
|
1609
|
+
...input.pipelineId !== void 0 ? { pipelineId: input.pipelineId } : {},
|
|
1610
|
+
...input.executionId !== void 0 ? { executionId: input.executionId } : {}
|
|
1611
|
+
},
|
|
1612
|
+
{ ...responder !== void 0 ? { responder } : {} }
|
|
1613
|
+
);
|
|
1614
|
+
for (const result of session.decoder.push(stdout)) {
|
|
1615
|
+
session.handle(result);
|
|
875
1616
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
`Agent "${agentName}" produced no protocol messages on stdout (input mode "protocol").`,
|
|
879
|
-
{ details: { taskId: input.taskId, stdout: stdout.slice(0, 2e3) } }
|
|
880
|
-
);
|
|
1617
|
+
for (const result of session.decoder.flush()) {
|
|
1618
|
+
session.handle(result);
|
|
881
1619
|
}
|
|
1620
|
+
const reports = session.reports.flatMap((payload) => Array.isArray(payload) ? payload : [payload]).map((payload, index) => normalizeReport(payload, input.taskId, agentName, index)).filter((report) => report !== void 0);
|
|
882
1621
|
const trimmed = stdout.trim();
|
|
1622
|
+
const protocolBlock = {
|
|
1623
|
+
protocol: session.descriptor?.protocol ?? PROTOCOL_V2,
|
|
1624
|
+
legacy: !session.sawV2 && session.descriptor === void 0,
|
|
1625
|
+
...session.descriptor !== void 0 ? { descriptor: session.descriptor } : {},
|
|
1626
|
+
questions: session.questions,
|
|
1627
|
+
violations: session.violations.map((v) => `${v.code}: ${v.message}`)
|
|
1628
|
+
};
|
|
1629
|
+
if (session.errorMessage !== void 0) {
|
|
1630
|
+
return {
|
|
1631
|
+
taskId: input.taskId,
|
|
1632
|
+
status: "failed",
|
|
1633
|
+
error: session.errorMessage,
|
|
1634
|
+
rawOutput: trimmed.length > 0 ? trimmed : void 0,
|
|
1635
|
+
exitCode: exitCode ?? void 0,
|
|
1636
|
+
logs,
|
|
1637
|
+
protocol: protocolBlock,
|
|
1638
|
+
...reports.length > 0 ? { reports } : {}
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
if (session.descriptor === void 0 && session.violations.length > 0 && session.reports.length === 0 && session.completionSummary === void 0) {
|
|
1642
|
+
return {
|
|
1643
|
+
taskId: input.taskId,
|
|
1644
|
+
status: "failed",
|
|
1645
|
+
error: `agent produced no valid protocol messages: ${protocolBlock.violations.join("; ")}`,
|
|
1646
|
+
rawOutput: trimmed.length > 0 ? trimmed : void 0,
|
|
1647
|
+
exitCode: exitCode ?? void 0,
|
|
1648
|
+
logs,
|
|
1649
|
+
protocol: protocolBlock
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
const completed = exitCode === 0 && session.completionSummary !== void 0;
|
|
883
1653
|
return {
|
|
884
1654
|
taskId: input.taskId,
|
|
885
|
-
status:
|
|
886
|
-
summary:
|
|
1655
|
+
status: completed || exitCode === 0 ? completed ? "completed" : "completed" : "failed",
|
|
1656
|
+
summary: session.completionSummary ?? reports[0]?.summary,
|
|
887
1657
|
rawOutput: trimmed.length > 0 ? trimmed : void 0,
|
|
888
1658
|
exitCode: exitCode ?? void 0,
|
|
889
1659
|
logs,
|
|
890
|
-
|
|
891
|
-
|
|
1660
|
+
protocol: protocolBlock,
|
|
1661
|
+
...reports.length > 0 ? { reports } : {},
|
|
1662
|
+
...exitCode !== 0 ? { error: `Process exited with code ${exitCode ?? "unknown"}` } : {}
|
|
892
1663
|
};
|
|
893
1664
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1665
|
+
var GLOBAL_RUNNING = /* @__PURE__ */ new Set();
|
|
1666
|
+
var signalForwardingInstalled = false;
|
|
1667
|
+
function installSignalForwarding() {
|
|
1668
|
+
if (signalForwardingInstalled) return;
|
|
1669
|
+
signalForwardingInstalled = true;
|
|
1670
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
1671
|
+
process.on(signal, () => {
|
|
1672
|
+
for (const child of GLOBAL_RUNNING) {
|
|
1673
|
+
killTree(child, "SIGTERM");
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
function killTree(child, signal) {
|
|
1679
|
+
if (child.pid === void 0) return;
|
|
1680
|
+
if (process.platform !== "win32") {
|
|
1681
|
+
try {
|
|
1682
|
+
process.kill(-child.pid, signal);
|
|
1683
|
+
return;
|
|
1684
|
+
} catch {
|
|
903
1685
|
}
|
|
1686
|
+
}
|
|
1687
|
+
try {
|
|
1688
|
+
child.kill(signal);
|
|
904
1689
|
} catch {
|
|
905
1690
|
}
|
|
906
|
-
|
|
1691
|
+
}
|
|
1692
|
+
function describeExit(code, signal) {
|
|
1693
|
+
if (signal !== null) {
|
|
1694
|
+
return signal === "SIGKILL" ? "Process was killed (SIGKILL) \u2014 usually an external kill or OOM." : `Process terminated by signal ${signal}.`;
|
|
1695
|
+
}
|
|
1696
|
+
if (code === null) return "Process terminated without an exit code.";
|
|
1697
|
+
return `Process exited with code ${code}.`;
|
|
1698
|
+
}
|
|
1699
|
+
function sanitizeChunk(chunk) {
|
|
1700
|
+
return [...chunk.toString("utf8")].filter((char) => char.charCodeAt(0) !== 0).join("");
|
|
907
1701
|
}
|
|
908
1702
|
function normalizeReport(payload, taskId, agentName, index) {
|
|
909
1703
|
if (payload === null || typeof payload !== "object") return void 0;
|
|
@@ -924,6 +1718,40 @@ function firstLine(text, maxLength = 200) {
|
|
|
924
1718
|
return line.length > maxLength ? `${line.slice(0, maxLength)}\u2026` : line;
|
|
925
1719
|
}
|
|
926
1720
|
|
|
1721
|
+
// src/agents/adapters.ts
|
|
1722
|
+
var AgentAdapterRegistry = class {
|
|
1723
|
+
adapters = /* @__PURE__ */ new Map();
|
|
1724
|
+
register(adapter) {
|
|
1725
|
+
this.adapters.set(adapter.integration, adapter);
|
|
1726
|
+
}
|
|
1727
|
+
get(integration) {
|
|
1728
|
+
return this.adapters.get(integration);
|
|
1729
|
+
}
|
|
1730
|
+
list() {
|
|
1731
|
+
return [...this.adapters.values()];
|
|
1732
|
+
}
|
|
1733
|
+
create(config) {
|
|
1734
|
+
const adapter = this.get(config.integration);
|
|
1735
|
+
if (!adapter) {
|
|
1736
|
+
throw new AgentError(
|
|
1737
|
+
"AGENT_NOT_FOUND",
|
|
1738
|
+
`No agent adapter registered for integration "${config.integration}". Registered: ${[...this.adapters.keys()].join(", ") || "(none)"}.`
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
return adapter.create(config);
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
var ProcessAgentAdapter = class {
|
|
1745
|
+
constructor(factory) {
|
|
1746
|
+
this.factory = factory;
|
|
1747
|
+
}
|
|
1748
|
+
factory;
|
|
1749
|
+
integration = "process";
|
|
1750
|
+
create(config) {
|
|
1751
|
+
return this.factory(config);
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
|
|
927
1755
|
// src/agents/registry.ts
|
|
928
1756
|
var AgentRegistry = class {
|
|
929
1757
|
agents = /* @__PURE__ */ new Map();
|
|
@@ -939,14 +1767,15 @@ var AgentRegistry = class {
|
|
|
939
1767
|
};
|
|
940
1768
|
|
|
941
1769
|
// src/agents/factory.ts
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1770
|
+
var defaultAgentAdapters = new AgentAdapterRegistry();
|
|
1771
|
+
defaultAgentAdapters.register(new ProcessAgentAdapter((config) => new ProcessAgent(config)));
|
|
1772
|
+
function createAgent(config, adapters = defaultAgentAdapters) {
|
|
1773
|
+
const registered = adapters.get(config.integration);
|
|
1774
|
+
if (registered) return registered.create(config);
|
|
946
1775
|
if (config.integration === "native") {
|
|
947
1776
|
throw new OmniNodeError(
|
|
948
1777
|
"NOT_IMPLEMENTED",
|
|
949
|
-
`
|
|
1778
|
+
`No native adapter is registered for "${config.name}". Register an IAgentAdapter (createAgent(config, adapters)) or use input over stdin/stdout (type "cli").`
|
|
950
1779
|
);
|
|
951
1780
|
}
|
|
952
1781
|
throw new OmniNodeError("NOT_IMPLEMENTED", `Agent integration "${config.integration}" has no adapter yet.`);
|
|
@@ -1082,12 +1911,18 @@ import { randomBytes } from "crypto";
|
|
|
1082
1911
|
var ALLOWED_TRANSITIONS = {
|
|
1083
1912
|
created: ["queued", "running", "cancelled"],
|
|
1084
1913
|
queued: ["running", "cancelled"],
|
|
1085
|
-
running: ["completed", "failed", "cancelled"],
|
|
1914
|
+
running: ["completed", "failed", "timed_out", "unknown", "cancelled", "partially_completed"],
|
|
1086
1915
|
waiting: ["running", "cancelled"],
|
|
1087
1916
|
completed: [],
|
|
1088
|
-
failed:
|
|
1089
|
-
|
|
1917
|
+
// failed → running is the only re-entry: bounded automatic retry (§6.4).
|
|
1918
|
+
failed: ["running"],
|
|
1919
|
+
timed_out: [],
|
|
1920
|
+
cancelled: [],
|
|
1921
|
+
unknown: [],
|
|
1922
|
+
partially_completed: []
|
|
1090
1923
|
};
|
|
1924
|
+
var RETRYABLE_ERROR_CODES = ["AGENT_FAILED"];
|
|
1925
|
+
var EXECUTION_HISTORY_LIMIT = 10;
|
|
1091
1926
|
var TaskEngine = class {
|
|
1092
1927
|
constructor(options) {
|
|
1093
1928
|
this.options = options;
|
|
@@ -1119,6 +1954,7 @@ var TaskEngine = class {
|
|
|
1119
1954
|
id: generateTaskId(),
|
|
1120
1955
|
objective: input.objective.trim(),
|
|
1121
1956
|
status: "created",
|
|
1957
|
+
attempt: 0,
|
|
1122
1958
|
createdAt: now,
|
|
1123
1959
|
updatedAt: now,
|
|
1124
1960
|
...input.project !== void 0 ? { project: input.project } : {},
|
|
@@ -1135,8 +1971,10 @@ var TaskEngine = class {
|
|
|
1135
1971
|
const task = await this.mustGet(id);
|
|
1136
1972
|
return this.transition(task, "queued");
|
|
1137
1973
|
}
|
|
1138
|
-
/** Runs the task through its assigned agent and records the result.
|
|
1139
|
-
|
|
1974
|
+
/** Runs the task through its assigned agent and records the result.
|
|
1975
|
+
* `maxAttempts` enables bounded automatic retries of *retryable* errors
|
|
1976
|
+
* with linear backoff; unknown/timed_out outcomes are never auto-retried. */
|
|
1977
|
+
async run(id, options = {}) {
|
|
1140
1978
|
const task = await this.mustGet(id);
|
|
1141
1979
|
if (task.status !== "created" && task.status !== "queued") {
|
|
1142
1980
|
throw new TaskError("TASK_INVALID", `Task ${id} is "${task.status}" and cannot be run.`);
|
|
@@ -1149,49 +1987,89 @@ var TaskEngine = class {
|
|
|
1149
1987
|
throw new TaskError("TASK_INVALID", `Agent "${task.agent}" is not registered anymore.`);
|
|
1150
1988
|
}
|
|
1151
1989
|
const role = task.role ? this.options.roles.get(task.role) : void 0;
|
|
1152
|
-
const running = await this.transition(task, "running");
|
|
1153
1990
|
this.log.info(`Running task ${task.id} on agent "${task.agent}".`);
|
|
1154
|
-
let background =
|
|
1991
|
+
let background = task.context?.background;
|
|
1155
1992
|
if (this.options.memory) {
|
|
1156
1993
|
const memoryContext = await this.options.memory.gatherContext({
|
|
1157
|
-
objective:
|
|
1158
|
-
...
|
|
1159
|
-
...
|
|
1994
|
+
objective: task.objective,
|
|
1995
|
+
...task.role ? { role: task.role } : {},
|
|
1996
|
+
...task.project ? { project: task.project } : {}
|
|
1160
1997
|
});
|
|
1161
1998
|
if (memoryContext.length > 0) {
|
|
1162
1999
|
background = [memoryContext, background].filter((part) => part && part.length > 0).join("\n\n");
|
|
1163
2000
|
}
|
|
1164
2001
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
2002
|
+
const maxAttempts = Math.max(1, options.maxAttempts ?? 1);
|
|
2003
|
+
const backoffMs = options.retryBackoffMs ?? 0;
|
|
2004
|
+
let current = task;
|
|
2005
|
+
for (let i = 0; i < maxAttempts; i += 1) {
|
|
2006
|
+
const attempt = current.attempt + 1;
|
|
2007
|
+
const executionId = `exec-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
2008
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2009
|
+
current = await this.transition(current, "running", { attempt });
|
|
2010
|
+
let output;
|
|
2011
|
+
let thrown;
|
|
2012
|
+
try {
|
|
2013
|
+
output = await agent.run({
|
|
2014
|
+
taskId: current.id,
|
|
2015
|
+
objective: current.objective,
|
|
2016
|
+
...role ? { role } : {},
|
|
2017
|
+
...background !== void 0 ? { context: background } : {}
|
|
2018
|
+
});
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
thrown = error;
|
|
2021
|
+
}
|
|
2022
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2023
|
+
if (thrown !== void 0 || output === void 0) {
|
|
2024
|
+
const outcome = classifyThrown(thrown);
|
|
2025
|
+
const message = thrown instanceof Error ? thrown.message : String(thrown);
|
|
2026
|
+
const retryable = outcome === "failed" && RETRYABLE_ERROR_CODES.includes(errorCode(thrown)) && i < maxAttempts - 1;
|
|
2027
|
+
current = await this.transition(current, outcome, {
|
|
2028
|
+
attempt,
|
|
2029
|
+
lastError: message,
|
|
2030
|
+
execution: {
|
|
2031
|
+
executionId,
|
|
2032
|
+
attempt,
|
|
2033
|
+
startedAt,
|
|
2034
|
+
finishedAt,
|
|
2035
|
+
outcome,
|
|
2036
|
+
error: message
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
2039
|
+
this.log.warn(`Task ${current.id} attempt ${attempt} \u2192 ${outcome}: ${message}`);
|
|
2040
|
+
if (retryable) {
|
|
2041
|
+
if (backoffMs > 0) await sleep(backoffMs * attempt);
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (this.options.memory) await this.options.memory.recordTaskOutcome(current);
|
|
2045
|
+
return current;
|
|
2046
|
+
}
|
|
2047
|
+
const currentState = await this.options.store.get(current.id);
|
|
2048
|
+
if (currentState?.status === "cancelled") {
|
|
2049
|
+
current = currentState;
|
|
2050
|
+
break;
|
|
2051
|
+
}
|
|
2052
|
+
const result = {
|
|
2053
|
+
...output.summary !== void 0 ? { summary: output.summary } : {},
|
|
2054
|
+
...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
|
|
2055
|
+
// Keep the verbatim output (bounded) for report extraction and audit.
|
|
2056
|
+
...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
|
|
2057
|
+
...output.error !== void 0 ? { error: output.error } : {},
|
|
2058
|
+
finishedAt
|
|
2059
|
+
};
|
|
2060
|
+
const finalStatus = output.status === "completed" ? "completed" : "failed";
|
|
2061
|
+
current = await this.transition(current, finalStatus, {
|
|
2062
|
+
attempt,
|
|
2063
|
+
result,
|
|
2064
|
+
execution: { executionId, attempt, startedAt, finishedAt, outcome: finalStatus }
|
|
1178
2065
|
});
|
|
2066
|
+
this.log.info(`Task ${current.id} finished: ${finalStatus}.`);
|
|
2067
|
+
break;
|
|
1179
2068
|
}
|
|
1180
|
-
const result = {
|
|
1181
|
-
...output.summary !== void 0 ? { summary: output.summary } : {},
|
|
1182
|
-
...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
|
|
1183
|
-
// Keep the verbatim output (bounded) for report extraction and audit.
|
|
1184
|
-
...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
|
|
1185
|
-
...output.error !== void 0 ? { error: output.error } : {},
|
|
1186
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1187
|
-
};
|
|
1188
|
-
const finalStatus = output.status === "completed" ? "completed" : "failed";
|
|
1189
|
-
const finished = await this.transition(running, finalStatus, { result });
|
|
1190
|
-
this.log.info(`Task ${task.id} finished: ${finalStatus}.`);
|
|
1191
2069
|
if (this.options.memory) {
|
|
1192
|
-
await this.options.memory.recordTaskOutcome(
|
|
2070
|
+
await this.options.memory.recordTaskOutcome(current);
|
|
1193
2071
|
}
|
|
1194
|
-
return
|
|
2072
|
+
return current;
|
|
1195
2073
|
}
|
|
1196
2074
|
async cancel(id) {
|
|
1197
2075
|
const task = await this.mustGet(id);
|
|
@@ -1204,13 +2082,14 @@ var TaskEngine = class {
|
|
|
1204
2082
|
}
|
|
1205
2083
|
return this.transition(task, "cancelled");
|
|
1206
2084
|
}
|
|
1207
|
-
/** Error recovery
|
|
2085
|
+
/** Error recovery: reset a failed/cancelled/unknown/timed_out task so it can run again. */
|
|
1208
2086
|
async retry(id) {
|
|
1209
2087
|
const task = await this.mustGet(id);
|
|
1210
|
-
|
|
2088
|
+
const retryableStates = ["failed", "cancelled", "unknown", "timed_out"];
|
|
2089
|
+
if (!retryableStates.includes(task.status)) {
|
|
1211
2090
|
throw new TaskError(
|
|
1212
2091
|
"TASK_INVALID",
|
|
1213
|
-
`Only failed or cancelled tasks can be retried \u2014 task ${id} is "${task.status}".`
|
|
2092
|
+
`Only failed, timed out, unknown or cancelled tasks can be retried \u2014 task ${id} is "${task.status}".`
|
|
1214
2093
|
);
|
|
1215
2094
|
}
|
|
1216
2095
|
const { result: _discarded, ...rest } = task;
|
|
@@ -1242,10 +2121,14 @@ var TaskEngine = class {
|
|
|
1242
2121
|
if (!allowed.includes(to)) {
|
|
1243
2122
|
throw new TaskError("TASK_INVALID", `Task ${task.id} cannot move from "${task.status}" to "${to}".`);
|
|
1244
2123
|
}
|
|
2124
|
+
const executions = extra.execution ? [...task.executions ?? [], extra.execution].slice(-EXECUTION_HISTORY_LIMIT) : task.executions;
|
|
1245
2125
|
const updated = {
|
|
1246
2126
|
...task,
|
|
1247
2127
|
status: to,
|
|
1248
2128
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2129
|
+
...extra.attempt !== void 0 ? { attempt: extra.attempt } : {},
|
|
2130
|
+
...extra.lastError !== void 0 ? { lastError: extra.lastError } : {},
|
|
2131
|
+
...executions !== void 0 ? { executions } : {},
|
|
1249
2132
|
...extra.result !== void 0 ? { result: { ...task.result, ...extra.result } } : {}
|
|
1250
2133
|
};
|
|
1251
2134
|
await this.options.store.save(updated);
|
|
@@ -1266,6 +2149,19 @@ var TaskEngine = class {
|
|
|
1266
2149
|
function generateTaskId() {
|
|
1267
2150
|
return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
1268
2151
|
}
|
|
2152
|
+
function errorCode(error) {
|
|
2153
|
+
return error?.code ?? "";
|
|
2154
|
+
}
|
|
2155
|
+
function classifyThrown(error) {
|
|
2156
|
+
if (error instanceof AgentError && error.code === "AGENT_TIMEOUT") {
|
|
2157
|
+
const dispatched = error.details?.dispatched;
|
|
2158
|
+
return dispatched === false ? "timed_out" : "unknown";
|
|
2159
|
+
}
|
|
2160
|
+
return "failed";
|
|
2161
|
+
}
|
|
2162
|
+
function sleep(ms) {
|
|
2163
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2164
|
+
}
|
|
1269
2165
|
|
|
1270
2166
|
// src/audit/index.ts
|
|
1271
2167
|
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
@@ -1308,6 +2204,23 @@ var FileAuditLog = class {
|
|
|
1308
2204
|
}
|
|
1309
2205
|
};
|
|
1310
2206
|
|
|
2207
|
+
// src/memory/base.ts
|
|
2208
|
+
function defaultSearch(provider, text, options = {}) {
|
|
2209
|
+
return provider.retrieve({
|
|
2210
|
+
text,
|
|
2211
|
+
...options.scope !== void 0 ? { scope: options.scope } : {},
|
|
2212
|
+
...options.limit !== void 0 ? { limit: options.limit } : {}
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
function defaultMetadata(name, options = {}) {
|
|
2216
|
+
return {
|
|
2217
|
+
name,
|
|
2218
|
+
capabilities: options.capabilities ?? ["retrieve", "store", "search"],
|
|
2219
|
+
relevanceRanking: options.relevanceRanking ?? false,
|
|
2220
|
+
...options.backend !== void 0 ? { backend: options.backend } : {}
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
|
|
1311
2224
|
// src/memory/local.ts
|
|
1312
2225
|
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
1313
2226
|
"the",
|
|
@@ -1370,9 +2283,27 @@ var LocalMemoryProvider = class extends JsonFileStore {
|
|
|
1370
2283
|
constructor(directory) {
|
|
1371
2284
|
super({ ...directory !== void 0 ? { directory } : {}, fileName: "memory.json" });
|
|
1372
2285
|
}
|
|
1373
|
-
|
|
2286
|
+
metadata() {
|
|
2287
|
+
return defaultMetadata(this.name, {
|
|
2288
|
+
capabilities: ["retrieve", "store", "search", "categories", "tags"],
|
|
2289
|
+
relevanceRanking: true,
|
|
2290
|
+
backend: { kind: "json-file", deterministicScoring: true }
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
search(text, options = {}) {
|
|
2294
|
+
return defaultSearch(this, text, options);
|
|
2295
|
+
}
|
|
2296
|
+
/** @deprecated v1 alias of {@link retrieve}. */
|
|
2297
|
+
query(query) {
|
|
2298
|
+
return this.retrieve(query);
|
|
2299
|
+
}
|
|
2300
|
+
/** @deprecated v1 alias of {@link store}. */
|
|
2301
|
+
write(entry) {
|
|
2302
|
+
return this.store(entry);
|
|
2303
|
+
}
|
|
2304
|
+
async retrieve(query) {
|
|
1374
2305
|
const entries = await this.readAll();
|
|
1375
|
-
const filtered = entries.filter((entry) => query.scope ? entry.scope === query.scope : true).filter(
|
|
2306
|
+
const filtered = entries.filter((entry) => query.scope ? entry.scope === query.scope : true).filter((entry) => query.category ? entry.category === query.category : true).filter(
|
|
1376
2307
|
(entry) => query.tags && query.tags.length > 0 ? query.tags.every((tag) => (entry.tags ?? []).includes(tag)) : true
|
|
1377
2308
|
);
|
|
1378
2309
|
if (!query.text || query.text.trim().length === 0) {
|
|
@@ -1381,7 +2312,7 @@ var LocalMemoryProvider = class extends JsonFileStore {
|
|
|
1381
2312
|
const queryKeywords = keywords(query.text);
|
|
1382
2313
|
return filtered.map((entry) => ({ entry, score: relevanceScore(queryKeywords, entry) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, query.limit ?? 10).map(({ entry }) => entry);
|
|
1383
2314
|
}
|
|
1384
|
-
|
|
2315
|
+
store(entry) {
|
|
1385
2316
|
return this.mutate((entries) => {
|
|
1386
2317
|
const index = entries.findIndex((e) => e.key === entry.key);
|
|
1387
2318
|
const stamped = {
|
|
@@ -1412,11 +2343,40 @@ var RememberaMemoryProvider = class {
|
|
|
1412
2343
|
this.env = config.env ?? process.env;
|
|
1413
2344
|
this.log = log.child({ component: "remembera" });
|
|
1414
2345
|
}
|
|
2346
|
+
get authenticationConfigured() {
|
|
2347
|
+
return this.apiKeyEnvVar === void 0 || Boolean(this.env[this.apiKeyEnvVar]);
|
|
2348
|
+
}
|
|
1415
2349
|
headers() {
|
|
1416
2350
|
const key = this.apiKeyEnvVar ? resolveApiKey({ name: "remembera", apiKeyEnvVar: this.apiKeyEnvVar }, this.env) : void 0;
|
|
1417
2351
|
return key ? { authorization: `Bearer ${key}` } : {};
|
|
1418
2352
|
}
|
|
1419
|
-
|
|
2353
|
+
/** Search delegates to the gateway's relevance query. */
|
|
2354
|
+
search(text, options = {}) {
|
|
2355
|
+
return defaultSearch(
|
|
2356
|
+
this,
|
|
2357
|
+
text,
|
|
2358
|
+
{
|
|
2359
|
+
...options.scope !== void 0 ? { scope: options.scope } : {},
|
|
2360
|
+
...options.limit !== void 0 ? { limit: options.limit } : {}
|
|
2361
|
+
}
|
|
2362
|
+
);
|
|
2363
|
+
}
|
|
2364
|
+
metadata() {
|
|
2365
|
+
return defaultMetadata(this.name, {
|
|
2366
|
+
capabilities: ["retrieve", "store", "search", "categories", "tags"],
|
|
2367
|
+
relevanceRanking: true,
|
|
2368
|
+
backend: { kind: "http", baseUrl: this.baseUrl, authenticated: this.authenticationConfigured }
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
/** @deprecated v1 alias of {@link retrieve}. */
|
|
2372
|
+
query(query) {
|
|
2373
|
+
return this.retrieve(query);
|
|
2374
|
+
}
|
|
2375
|
+
/** @deprecated v1 alias of {@link store}. */
|
|
2376
|
+
write(entry) {
|
|
2377
|
+
return this.store(entry);
|
|
2378
|
+
}
|
|
2379
|
+
async retrieve(query) {
|
|
1420
2380
|
const { status, body } = await this.request(`${this.baseUrl}/api/memory/query`, query);
|
|
1421
2381
|
if (status !== 200) {
|
|
1422
2382
|
throw new MemoryError(`Remembera query returned HTTP ${status}.`);
|
|
@@ -1427,7 +2387,7 @@ var RememberaMemoryProvider = class {
|
|
|
1427
2387
|
}
|
|
1428
2388
|
return entries.map((entry) => this.toEntry(entry)).filter((entry) => entry !== void 0);
|
|
1429
2389
|
}
|
|
1430
|
-
async
|
|
2390
|
+
async store(entry) {
|
|
1431
2391
|
const { status } = await this.request(`${this.baseUrl}/api/memory`, entry);
|
|
1432
2392
|
if (status !== 200 && status !== 201) {
|
|
1433
2393
|
throw new MemoryError(`Remembera write returned HTTP ${status}.`);
|
|
@@ -1462,6 +2422,7 @@ var RememberaMemoryProvider = class {
|
|
|
1462
2422
|
content: entry.content,
|
|
1463
2423
|
scope: scope === "project" || scope === "role" || scope === "task" || scope === "knowledge" ? scope : "knowledge",
|
|
1464
2424
|
...Array.isArray(entry.tags) ? { tags: entry.tags.filter((t) => typeof t === "string") } : {},
|
|
2425
|
+
...typeof entry.category === "string" ? { category: entry.category } : {},
|
|
1465
2426
|
...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
|
|
1466
2427
|
...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {}
|
|
1467
2428
|
};
|
|
@@ -1492,57 +2453,54 @@ function createMemoryProvider(config) {
|
|
|
1492
2453
|
}
|
|
1493
2454
|
|
|
1494
2455
|
// src/memory/service.ts
|
|
2456
|
+
var DEFAULT_ENTRY_LIMIT = 8;
|
|
1495
2457
|
var MAX_CONTEXT_CHARS = 4e3;
|
|
1496
2458
|
var MemoryService = class {
|
|
1497
|
-
constructor(provider, log = logger) {
|
|
2459
|
+
constructor(provider, log = logger, options = {}) {
|
|
1498
2460
|
this.provider = provider;
|
|
1499
2461
|
this.log = log.child({ component: "memory", provider: provider.name });
|
|
2462
|
+
this.required = options.required ?? false;
|
|
2463
|
+
this.maxContextChars = options.maxContextChars ?? MAX_CONTEXT_CHARS;
|
|
1500
2464
|
}
|
|
1501
2465
|
provider;
|
|
1502
2466
|
log;
|
|
2467
|
+
required;
|
|
2468
|
+
maxContextChars;
|
|
1503
2469
|
get providerRef() {
|
|
1504
2470
|
return this.provider;
|
|
1505
2471
|
}
|
|
1506
2472
|
get providerName() {
|
|
1507
2473
|
return this.provider.name;
|
|
1508
2474
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
2475
|
+
get isRequired() {
|
|
2476
|
+
return this.required;
|
|
2477
|
+
}
|
|
2478
|
+
/** Structured, budgeted context for a task (§12 — avoid loading all memory). */
|
|
2479
|
+
async contextFor(query) {
|
|
2480
|
+
let entries;
|
|
1511
2481
|
try {
|
|
1512
|
-
|
|
2482
|
+
entries = await this.provider.retrieve({
|
|
1513
2483
|
text: query.objective,
|
|
1514
2484
|
...query.role ? { tags: [query.role] } : {},
|
|
1515
|
-
limit:
|
|
2485
|
+
limit: query.limit ?? DEFAULT_ENTRY_LIMIT
|
|
1516
2486
|
});
|
|
1517
|
-
if (entries.length === 0) return "";
|
|
1518
|
-
const byScope = /* @__PURE__ */ new Map();
|
|
1519
|
-
for (const entry of entries) {
|
|
1520
|
-
byScope.set(entry.scope, [...byScope.get(entry.scope) ?? [], entry]);
|
|
1521
|
-
}
|
|
1522
|
-
const parts = [];
|
|
1523
|
-
for (const scope of ["project", "task", "role", "knowledge"]) {
|
|
1524
|
-
const scopeEntries = byScope.get(scope) ?? [];
|
|
1525
|
-
if (scopeEntries.length === 0) continue;
|
|
1526
|
-
const lines = scopeEntries.map((entry) => `- ${entry.content}`).join("\n");
|
|
1527
|
-
parts.push(`### Relevant memory (${scope})
|
|
1528
|
-
${lines}`);
|
|
1529
|
-
}
|
|
1530
|
-
const context = parts.join("\n\n");
|
|
1531
|
-
return context.length > MAX_CONTEXT_CHARS ? `${context.slice(0, MAX_CONTEXT_CHARS)}\u2026` : context;
|
|
1532
2487
|
} catch (error) {
|
|
1533
|
-
this.
|
|
1534
|
-
`Memory query failed (continuing without memory): ${error instanceof Error ? error.message : String(error)}`
|
|
1535
|
-
);
|
|
1536
|
-
return "";
|
|
2488
|
+
return this.handleFailure("retrieve", error, { text: "", entries: [], truncated: false });
|
|
1537
2489
|
}
|
|
2490
|
+
return this.format(entries);
|
|
1538
2491
|
}
|
|
1539
|
-
/**
|
|
2492
|
+
/** Prompt-ready context text (v1 convenience wrapper). */
|
|
2493
|
+
async gatherContext(query) {
|
|
2494
|
+
return (await this.contextFor(query)).text;
|
|
2495
|
+
}
|
|
2496
|
+
/** Records a finished task's outcome — task history plus "known problems" (§12). */
|
|
1540
2497
|
async recordTaskOutcome(task) {
|
|
1541
2498
|
const outcome = task.result?.summary ?? task.result?.error ?? "(no output)";
|
|
1542
2499
|
try {
|
|
1543
|
-
await this.provider.
|
|
2500
|
+
await this.provider.store({
|
|
1544
2501
|
key: `task:${task.id}`,
|
|
1545
2502
|
scope: "task",
|
|
2503
|
+
category: "tasks",
|
|
1546
2504
|
content: `Objective: ${task.objective}
|
|
1547
2505
|
Status: ${task.status}
|
|
1548
2506
|
Outcome: ${outcome}`,
|
|
@@ -1553,18 +2511,53 @@ Outcome: ${outcome}`,
|
|
|
1553
2511
|
(report) => report.findings.filter((finding) => finding.severity === "high" || finding.severity === "critical").map((finding) => ({ report, finding }))
|
|
1554
2512
|
);
|
|
1555
2513
|
for (const { report, finding } of significant) {
|
|
1556
|
-
await this.provider.
|
|
2514
|
+
await this.provider.store({
|
|
1557
2515
|
key: `finding:${report.id}:${finding.id}`,
|
|
1558
2516
|
scope: "project",
|
|
2517
|
+
category: "problems",
|
|
1559
2518
|
content: `[${finding.severity}] ${finding.title} \u2014 ${finding.detail}`,
|
|
1560
2519
|
tags: ["known-problem", report.agent]
|
|
1561
2520
|
});
|
|
1562
2521
|
}
|
|
1563
2522
|
} catch (error) {
|
|
1564
|
-
this.
|
|
1565
|
-
|
|
2523
|
+
this.handleFailure("store", error, void 0);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
format(entries) {
|
|
2527
|
+
if (entries.length === 0) return { text: "", entries, truncated: false };
|
|
2528
|
+
const byScope = /* @__PURE__ */ new Map();
|
|
2529
|
+
for (const entry of entries) {
|
|
2530
|
+
byScope.set(entry.scope, [...byScope.get(entry.scope) ?? [], entry]);
|
|
2531
|
+
}
|
|
2532
|
+
const parts = [];
|
|
2533
|
+
for (const scope of ["project", "task", "role", "knowledge"]) {
|
|
2534
|
+
const scopeEntries = byScope.get(scope) ?? [];
|
|
2535
|
+
if (scopeEntries.length === 0) continue;
|
|
2536
|
+
const lines = scopeEntries.map((entry) => {
|
|
2537
|
+
const category = entry.category ? `[${entry.category}] ` : "";
|
|
2538
|
+
return `- ${category}${entry.content}`;
|
|
2539
|
+
}).join("\n");
|
|
2540
|
+
parts.push(`### Relevant memory (${scope})
|
|
2541
|
+
${lines}`);
|
|
2542
|
+
}
|
|
2543
|
+
let text = parts.join("\n\n");
|
|
2544
|
+
const truncated = text.length > this.maxContextChars;
|
|
2545
|
+
if (truncated) text = `${text.slice(0, this.maxContextChars - 1)}\u2026`;
|
|
2546
|
+
return { text, entries, truncated };
|
|
2547
|
+
}
|
|
2548
|
+
/**
|
|
2549
|
+
* Failure policy (§12): continue unless memory is explicitly required.
|
|
2550
|
+
*/
|
|
2551
|
+
handleFailure(operation, error, fallback) {
|
|
2552
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2553
|
+
if (this.required) {
|
|
2554
|
+
throw new MemoryError(
|
|
2555
|
+
`Memory is marked required, but ${operation} failed: ${message}`,
|
|
2556
|
+
{ cause: error }
|
|
1566
2557
|
);
|
|
1567
2558
|
}
|
|
2559
|
+
this.log.warn(`Memory ${operation} failed (continuing without memory): ${message}`);
|
|
2560
|
+
return fallback ?? { text: "", entries: [], truncated: false };
|
|
1568
2561
|
}
|
|
1569
2562
|
};
|
|
1570
2563
|
|
|
@@ -1576,7 +2569,11 @@ function createTaskEngine(config, log = logger, directory) {
|
|
|
1576
2569
|
agents,
|
|
1577
2570
|
roles,
|
|
1578
2571
|
store: new FileTaskStore(directory),
|
|
1579
|
-
...config.project.memory !== void 0 ? {
|
|
2572
|
+
...config.project.memory !== void 0 ? {
|
|
2573
|
+
memory: new MemoryService(createMemoryProvider(config.project.memory), log, {
|
|
2574
|
+
required: config.project.memory.required
|
|
2575
|
+
})
|
|
2576
|
+
} : {},
|
|
1580
2577
|
audit: new FileAuditLog(),
|
|
1581
2578
|
log
|
|
1582
2579
|
});
|
|
@@ -1815,9 +2812,23 @@ var PipelineEngine = class {
|
|
|
1815
2812
|
}
|
|
1816
2813
|
options;
|
|
1817
2814
|
log;
|
|
2815
|
+
/** Runs executing in THIS process — cancellation can terminate them directly. */
|
|
2816
|
+
activeRuns = /* @__PURE__ */ new Map();
|
|
1818
2817
|
get store() {
|
|
1819
2818
|
return this.options.store;
|
|
1820
2819
|
}
|
|
2820
|
+
/**
|
|
2821
|
+
* Saves the run while preserving fields that may be set by another process
|
|
2822
|
+
* (e.g. a cancellation request) — an in-memory snapshot must never clobber
|
|
2823
|
+
* externally requested cancellation (§9.2).
|
|
2824
|
+
*/
|
|
2825
|
+
async persist(run) {
|
|
2826
|
+
const current = await this.options.store.get(run.id);
|
|
2827
|
+
await this.options.store.save({
|
|
2828
|
+
...run,
|
|
2829
|
+
...current?.cancellationRequested === true ? { cancellationRequested: true, cancellationRequestedAt: current.cancellationRequestedAt } : {}
|
|
2830
|
+
});
|
|
2831
|
+
}
|
|
1821
2832
|
/** Structural + semantic validation (§27 Phase 5 — pipeline parser). */
|
|
1822
2833
|
validate(def) {
|
|
1823
2834
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -1917,18 +2928,44 @@ var PipelineEngine = class {
|
|
|
1917
2928
|
);
|
|
1918
2929
|
}
|
|
1919
2930
|
}
|
|
2931
|
+
if (step.model) {
|
|
2932
|
+
const separator = step.model.indexOf(":");
|
|
2933
|
+
if (separator <= 0 || separator === step.model.length - 1) {
|
|
2934
|
+
throw new PipelineError(
|
|
2935
|
+
"PIPELINE_INVALID",
|
|
2936
|
+
`Step "${step.id}" has an invalid model reference "${step.model}" \u2014 use "provider:model-id".`
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
const providerName = step.model.slice(0, separator);
|
|
2940
|
+
if (!this.options.providers.some((p) => p.name === providerName)) {
|
|
2941
|
+
throw new PipelineError(
|
|
2942
|
+
"PIPELINE_INVALID",
|
|
2943
|
+
`Step "${step.id}" uses model "${step.model}" but provider "${providerName}" is not configured.`
|
|
2944
|
+
);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
1920
2947
|
}
|
|
1921
2948
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1922
2949
|
const run = {
|
|
1923
2950
|
id: `run-${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`,
|
|
1924
2951
|
pipelineId: def.id,
|
|
2952
|
+
attempt: 1,
|
|
1925
2953
|
objective,
|
|
1926
2954
|
status: "running",
|
|
1927
2955
|
startedAt: now,
|
|
1928
2956
|
stepRuns: []
|
|
1929
2957
|
};
|
|
1930
2958
|
await this.options.store.save(run);
|
|
1931
|
-
|
|
2959
|
+
const liveTaskIds = /* @__PURE__ */ new Set();
|
|
2960
|
+
this.activeRuns.set(run.id, {
|
|
2961
|
+
taskIds: liveTaskIds,
|
|
2962
|
+
cancelTasks: async () => {
|
|
2963
|
+
for (const taskId of liveTaskIds) {
|
|
2964
|
+
await this.options.tasks.cancel(taskId).catch(() => void 0);
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2968
|
+
this.log.info(`Running pipeline ${def.id} as ${run.id} (attempt ${run.attempt}).`);
|
|
1932
2969
|
await this.options.audit?.record({
|
|
1933
2970
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1934
2971
|
action: "pipeline.run.started",
|
|
@@ -1940,7 +2977,22 @@ var PipelineEngine = class {
|
|
|
1940
2977
|
const pending = new Set(def.steps.map((s) => s.id));
|
|
1941
2978
|
const contextParts = [];
|
|
1942
2979
|
const allReports = [];
|
|
2980
|
+
let cancelled = false;
|
|
1943
2981
|
while (pending.size > 0) {
|
|
2982
|
+
const persisted = await this.options.store.get(run.id);
|
|
2983
|
+
if (persisted?.cancellationRequested === true) {
|
|
2984
|
+
cancelled = true;
|
|
2985
|
+
run.cancellationRequested = true;
|
|
2986
|
+
run.cancellationRequestedAt = persisted.cancellationRequestedAt;
|
|
2987
|
+
await this.activeRuns.get(run.id)?.cancelTasks();
|
|
2988
|
+
for (const step of def.steps.filter((s) => pending.has(s.id))) {
|
|
2989
|
+
const stepRun = { stepId: step.id, status: "cancelled" };
|
|
2990
|
+
run.stepRuns.push(stepRun);
|
|
2991
|
+
options.onStep?.(stepRun);
|
|
2992
|
+
}
|
|
2993
|
+
pending.clear();
|
|
2994
|
+
break;
|
|
2995
|
+
}
|
|
1944
2996
|
const ready = def.steps.filter(
|
|
1945
2997
|
(step) => pending.has(step.id) && (deps.get(step.id) ?? []).every((d) => outcomes.has(d))
|
|
1946
2998
|
);
|
|
@@ -1957,7 +3009,8 @@ var PipelineEngine = class {
|
|
|
1957
3009
|
...role ? { role } : {},
|
|
1958
3010
|
combinedContext: contextParts.join("\n\n"),
|
|
1959
3011
|
reports: [...allReports],
|
|
1960
|
-
pipelineRunId: run.id
|
|
3012
|
+
pipelineRunId: run.id,
|
|
3013
|
+
runId: run.id
|
|
1961
3014
|
};
|
|
1962
3015
|
return this.executeStep(step, ctx);
|
|
1963
3016
|
})
|
|
@@ -1974,7 +3027,9 @@ ${contribution}`);
|
|
|
1974
3027
|
}
|
|
1975
3028
|
const stepRun = {
|
|
1976
3029
|
stepId: step.id,
|
|
1977
|
-
|
|
3030
|
+
attempt: outcome.attempt ?? 1,
|
|
3031
|
+
...outcome.executionIds && outcome.executionIds.length > 0 ? { executionIds: outcome.executionIds } : {},
|
|
3032
|
+
status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : outcome.status === "partial" ? "partially_completed" : "completed",
|
|
1978
3033
|
...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
|
|
1979
3034
|
...outcome.planId !== void 0 ? { planId: outcome.planId } : {},
|
|
1980
3035
|
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
@@ -1983,10 +3038,12 @@ ${contribution}`);
|
|
|
1983
3038
|
options.onStep?.(stepRun);
|
|
1984
3039
|
});
|
|
1985
3040
|
run.reports = [...allReports];
|
|
1986
|
-
await this.
|
|
3041
|
+
await this.persist(run);
|
|
1987
3042
|
}
|
|
1988
|
-
const
|
|
1989
|
-
const
|
|
3043
|
+
const allOutcomes = [...outcomes.values()];
|
|
3044
|
+
const failed = !cancelled && allOutcomes.some((o) => o.status === "failed");
|
|
3045
|
+
const partial = !cancelled && !failed && allOutcomes.some((o) => o.status === "partial");
|
|
3046
|
+
const finalStatus = cancelled ? "cancelled" : failed ? "failed" : partial ? "partial" : "completed";
|
|
1990
3047
|
const planId = [...outcomes.values()].find((o) => o.planId !== void 0)?.planId;
|
|
1991
3048
|
let resultSummary;
|
|
1992
3049
|
for (let i = def.steps.length - 1; i >= 0; i -= 1) {
|
|
@@ -2014,16 +3071,19 @@ ${contribution}`);
|
|
|
2014
3071
|
run.reports = collected;
|
|
2015
3072
|
}
|
|
2016
3073
|
}
|
|
3074
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3075
|
+
this.activeRuns.delete(run.id);
|
|
2017
3076
|
const finished = {
|
|
2018
3077
|
...run,
|
|
2019
3078
|
status: finalStatus,
|
|
2020
|
-
finishedAt
|
|
3079
|
+
finishedAt,
|
|
3080
|
+
attempts: [...run.attempts ?? [], { attempt: run.attempt, startedAt: run.startedAt ?? finishedAt, finishedAt, status: finalStatus }],
|
|
2021
3081
|
...run.reports !== void 0 && run.reports.length > 0 ? { reports: run.reports } : {},
|
|
2022
3082
|
...combinedReportId !== void 0 ? { combinedReportId } : {},
|
|
2023
3083
|
...planId !== void 0 ? { planId } : {},
|
|
2024
3084
|
...resultSummary !== void 0 ? { resultSummary } : {}
|
|
2025
3085
|
};
|
|
2026
|
-
await this.
|
|
3086
|
+
await this.persist(finished);
|
|
2027
3087
|
this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
|
|
2028
3088
|
await this.options.audit?.record({
|
|
2029
3089
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2033,6 +3093,23 @@ ${contribution}`);
|
|
|
2033
3093
|
});
|
|
2034
3094
|
return finished;
|
|
2035
3095
|
}
|
|
3096
|
+
/** First-class cancellation (§9.2): requests cancellation and terminates
|
|
3097
|
+
* tasks that are running in this process immediately. */
|
|
3098
|
+
async cancel(runId) {
|
|
3099
|
+
const run = await this.options.store.get(runId);
|
|
3100
|
+
if (!run) {
|
|
3101
|
+
throw new PipelineError("PIPELINE_NOT_FOUND", `Pipeline run "${runId}" does not exist.`);
|
|
3102
|
+
}
|
|
3103
|
+
await this.options.store.save({
|
|
3104
|
+
...run,
|
|
3105
|
+
cancellationRequested: true,
|
|
3106
|
+
cancellationRequestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3107
|
+
});
|
|
3108
|
+
const active = this.activeRuns.get(runId);
|
|
3109
|
+
if (active) {
|
|
3110
|
+
await active.cancelTasks();
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
2036
3113
|
async get(runId) {
|
|
2037
3114
|
return this.options.store.get(runId);
|
|
2038
3115
|
}
|
|
@@ -2056,9 +3133,16 @@ ${contribution}`);
|
|
|
2056
3133
|
this.log.warn(`Step "${step.id}" failed \u2014 retry ${attempt}/${retries}.`);
|
|
2057
3134
|
}
|
|
2058
3135
|
last = await this.executeStepOnce(step, ctx);
|
|
2059
|
-
if (last.status !== "failed") return last;
|
|
3136
|
+
if (last.status !== "failed") return { ...last, attempt: attempt + 1 };
|
|
2060
3137
|
}
|
|
2061
|
-
return last;
|
|
3138
|
+
return { ...last, attempt: retries + 1 };
|
|
3139
|
+
}
|
|
3140
|
+
/** Registers tasks created for a step as live, so cancellation can terminate them (§9.2). */
|
|
3141
|
+
trackLiveTasks(runId, taskIds) {
|
|
3142
|
+
if (!runId) return;
|
|
3143
|
+
const active = this.activeRuns.get(runId);
|
|
3144
|
+
if (!active) return;
|
|
3145
|
+
for (const id of taskIds) active.taskIds.add(id);
|
|
2062
3146
|
}
|
|
2063
3147
|
async executeStepOnce(step, ctx) {
|
|
2064
3148
|
switch (step.kind) {
|
|
@@ -2091,7 +3175,10 @@ ${contribution}`);
|
|
|
2091
3175
|
})
|
|
2092
3176
|
)
|
|
2093
3177
|
);
|
|
3178
|
+
this.trackLiveTasks(ctx.runId, created.map((t) => t.id));
|
|
2094
3179
|
const results = await Promise.all(created.map((task) => this.options.tasks.run(task.id)));
|
|
3180
|
+
const executionIds = results.map((r) => r.executions?.at(-1)?.executionId).filter((id) => id !== void 0);
|
|
3181
|
+
const completedTasks = results.filter((r) => r.status === "completed");
|
|
2095
3182
|
const failedTasks = results.filter((r) => r.status !== "completed");
|
|
2096
3183
|
const lines = results.map((result, index) => {
|
|
2097
3184
|
const agentName = agentNames[index] ?? "unknown";
|
|
@@ -2101,16 +3188,25 @@ ${contribution}`);
|
|
|
2101
3188
|
const reports = results.flatMap((r) => r.result?.reports ?? []);
|
|
2102
3189
|
const summary = lines.join("\n");
|
|
2103
3190
|
if (failedTasks.length > 0) {
|
|
2104
|
-
const firstError = failedTasks[0]?.result?.error ?? "unknown
|
|
3191
|
+
const firstError = failedTasks[0]?.result?.error ?? failedTasks[0]?.lastError ?? "unknown outcome";
|
|
3192
|
+
const states = failedTasks.map((r) => r.status).join(", ");
|
|
2105
3193
|
return {
|
|
2106
|
-
|
|
3194
|
+
// Some agents succeeded → the step produced usable but incomplete
|
|
3195
|
+
// intelligence: partial, not failed (roadmap §9.3).
|
|
3196
|
+
status: completedTasks.length > 0 ? "partial" : "failed",
|
|
2107
3197
|
summary,
|
|
2108
3198
|
taskIds: created.map((t) => t.id),
|
|
2109
|
-
error: `${failedTasks.length}/${results.length} task(s)
|
|
3199
|
+
error: `${failedTasks.length}/${results.length} task(s) incomplete (${states}) \u2014 ${firstError}`,
|
|
2110
3200
|
reports
|
|
2111
3201
|
};
|
|
2112
3202
|
}
|
|
2113
|
-
return {
|
|
3203
|
+
return {
|
|
3204
|
+
status: "completed",
|
|
3205
|
+
summary,
|
|
3206
|
+
taskIds: created.map((t) => t.id),
|
|
3207
|
+
...executionIds.length > 0 ? { executionIds } : {},
|
|
3208
|
+
reports
|
|
3209
|
+
};
|
|
2114
3210
|
}
|
|
2115
3211
|
async runSingle(step, ctx, agentName) {
|
|
2116
3212
|
const task = await this.options.tasks.create({
|
|
@@ -2119,10 +3215,13 @@ ${contribution}`);
|
|
|
2119
3215
|
...ctx.role ? { role: ctx.role.id } : {},
|
|
2120
3216
|
...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
|
|
2121
3217
|
});
|
|
3218
|
+
this.trackLiveTasks(ctx.runId, [task.id]);
|
|
2122
3219
|
const result = await this.options.tasks.run(task.id);
|
|
2123
3220
|
if (result.status !== "completed") {
|
|
2124
3221
|
return {
|
|
2125
|
-
|
|
3222
|
+
// Unknown/timed_out outcomes are not failures — the work may have
|
|
3223
|
+
// happened remotely; the step is incomplete, not broken.
|
|
3224
|
+
status: result.status === "unknown" || result.status === "timed_out" ? "partial" : "failed",
|
|
2126
3225
|
summary: result.result?.summary ?? "",
|
|
2127
3226
|
taskIds: [task.id],
|
|
2128
3227
|
error: result.result?.error ?? `task ${task.id} did not complete`,
|
|
@@ -2302,6 +3401,94 @@ function buildPlanner(plannerConfig, chat, log = logger) {
|
|
|
2302
3401
|
|
|
2303
3402
|
// src/reports/aggregate.ts
|
|
2304
3403
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
3404
|
+
|
|
3405
|
+
// src/reports/schema.ts
|
|
3406
|
+
import { z as z3 } from "zod";
|
|
3407
|
+
var evidenceSchema = z3.union([
|
|
3408
|
+
z3.string().transform((excerpt) => ({ kind: "observation", excerpt })),
|
|
3409
|
+
z3.object({
|
|
3410
|
+
kind: z3.enum(["file", "line", "url", "command", "observation", "artifact"]),
|
|
3411
|
+
ref: z3.string().optional(),
|
|
3412
|
+
line: z3.number().int().optional(),
|
|
3413
|
+
excerpt: z3.string().optional(),
|
|
3414
|
+
agent: z3.string().optional()
|
|
3415
|
+
})
|
|
3416
|
+
]);
|
|
3417
|
+
var findingSchema = z3.object({
|
|
3418
|
+
id: z3.string().min(1),
|
|
3419
|
+
title: z3.string().min(1),
|
|
3420
|
+
detail: z3.string().default(""),
|
|
3421
|
+
description: z3.string().optional(),
|
|
3422
|
+
severity: z3.enum(["info", "low", "medium", "high", "critical"]).optional(),
|
|
3423
|
+
confidence: z3.enum(["low", "medium", "high"]).optional(),
|
|
3424
|
+
severityBySource: z3.record(z3.enum(["info", "low", "medium", "high", "critical"])).optional(),
|
|
3425
|
+
confidenceBySource: z3.record(z3.enum(["low", "medium", "high"])).optional(),
|
|
3426
|
+
recommendations: z3.array(z3.string()).optional(),
|
|
3427
|
+
evidence: z3.array(evidenceSchema).optional(),
|
|
3428
|
+
location: z3.string().optional(),
|
|
3429
|
+
tags: z3.array(z3.string()).optional(),
|
|
3430
|
+
source: z3.object({ agent: z3.string().optional(), reportId: z3.string().optional(), pipelineRunId: z3.string().optional() }).optional(),
|
|
3431
|
+
recommendation: z3.string().optional()
|
|
3432
|
+
});
|
|
3433
|
+
var artifactSchema = z3.object({
|
|
3434
|
+
kind: z3.enum(["file", "patch", "log", "screenshot", "document", "data"]),
|
|
3435
|
+
ref: z3.string().min(1),
|
|
3436
|
+
description: z3.string().optional(),
|
|
3437
|
+
mediaType: z3.string().optional(),
|
|
3438
|
+
bytes: z3.number().int().optional()
|
|
3439
|
+
});
|
|
3440
|
+
var reportSchema = z3.object({
|
|
3441
|
+
id: z3.string().min(1),
|
|
3442
|
+
taskId: z3.string().min(1),
|
|
3443
|
+
agent: z3.string().min(1),
|
|
3444
|
+
agentId: z3.string().optional(),
|
|
3445
|
+
pipelineRunId: z3.string().optional(),
|
|
3446
|
+
model: z3.string().optional(),
|
|
3447
|
+
summary: z3.string(),
|
|
3448
|
+
findings: z3.array(findingSchema),
|
|
3449
|
+
recommendations: z3.array(z3.string()),
|
|
3450
|
+
confidence: z3.enum(["low", "medium", "high"]).optional(),
|
|
3451
|
+
evidence: z3.array(evidenceSchema).optional(),
|
|
3452
|
+
artifacts: z3.array(artifactSchema).optional(),
|
|
3453
|
+
createdAt: z3.string().min(1),
|
|
3454
|
+
metadata: z3.record(z3.unknown()).optional()
|
|
3455
|
+
});
|
|
3456
|
+
function validateReport(value) {
|
|
3457
|
+
const parsed = reportSchema.safeParse(value);
|
|
3458
|
+
if (!parsed.success) {
|
|
3459
|
+
const issues = parsed.error.issues.map((issue) => ({
|
|
3460
|
+
path: issue.path.join(".") || "(root)",
|
|
3461
|
+
message: issue.message
|
|
3462
|
+
}));
|
|
3463
|
+
return {
|
|
3464
|
+
valid: false,
|
|
3465
|
+
issues,
|
|
3466
|
+
reason: `report failed schema validation (${issues.length} issue(s))`
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
const report = parsed.data;
|
|
3470
|
+
report.findings = report.findings.map((finding) => ({
|
|
3471
|
+
...finding,
|
|
3472
|
+
description: finding.description ?? finding.detail,
|
|
3473
|
+
...finding.source === void 0 ? { source: { agent: report.agent, reportId: report.id, ...report.pipelineRunId !== void 0 ? { pipelineRunId: report.pipelineRunId } : {} } } : {}
|
|
3474
|
+
}));
|
|
3475
|
+
return { valid: true, report };
|
|
3476
|
+
}
|
|
3477
|
+
function normalizeEvidence(input) {
|
|
3478
|
+
if (input === void 0) return void 0;
|
|
3479
|
+
return input.map(
|
|
3480
|
+
(item) => typeof item === "string" ? { kind: "observation", excerpt: item } : item
|
|
3481
|
+
);
|
|
3482
|
+
}
|
|
3483
|
+
function normalizeFinding(finding) {
|
|
3484
|
+
return {
|
|
3485
|
+
...finding,
|
|
3486
|
+
description: finding.description ?? finding.detail,
|
|
3487
|
+
evidence: normalizeEvidence(finding.evidence)
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
// src/reports/aggregate.ts
|
|
2305
3492
|
var SEVERITY_RANK2 = {
|
|
2306
3493
|
info: 0,
|
|
2307
3494
|
low: 1,
|
|
@@ -2323,7 +3510,12 @@ function aggregateReports(reports, options = {}) {
|
|
|
2323
3510
|
}
|
|
2324
3511
|
const sources = [...new Set(reports.map((r) => r.agent))].sort();
|
|
2325
3512
|
const totalRawFindings = reports.reduce((sum, r) => sum + r.findings.length, 0);
|
|
2326
|
-
const
|
|
3513
|
+
const conflicts = detectConflicts(findingGroups);
|
|
3514
|
+
const agreements = findingGroups.filter(
|
|
3515
|
+
(group) => group.sources.length > 1 && group.severity !== void 0 && new Set(Object.values(group.severityBySource ?? {})).size === 1
|
|
3516
|
+
);
|
|
3517
|
+
const disputed = conflicts.length;
|
|
3518
|
+
const summary = `${reports.length} report(s) from ${sources.length > 0 ? sources.join(", ") : "no agents"}: ${findingGroups.length} unique finding group(s) (${totalRawFindings} raw), ${agreements.length} agreement(s), ${disputed} conflict(s), ${recommendationGroups.length} recommendation group(s).`;
|
|
2327
3519
|
return {
|
|
2328
3520
|
id: `combined-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`,
|
|
2329
3521
|
title: options.objective !== void 0 ? truncate2(options.objective, 120) : "Combined report",
|
|
@@ -2332,10 +3524,57 @@ function aggregateReports(reports, options = {}) {
|
|
|
2332
3524
|
taskIds: options.taskIds ?? [],
|
|
2333
3525
|
summary,
|
|
2334
3526
|
findings: findingGroups.sort((a, b) => rank(b) - rank(a)),
|
|
3527
|
+
agreements,
|
|
3528
|
+
conflicts,
|
|
2335
3529
|
recommendations: recommendationGroups,
|
|
2336
3530
|
sources,
|
|
2337
3531
|
reportIds: reports.map((r) => r.id),
|
|
2338
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3532
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3533
|
+
metadata: {
|
|
3534
|
+
agents: sources.length,
|
|
3535
|
+
reports: reports.length,
|
|
3536
|
+
rawFindings: totalRawFindings,
|
|
3537
|
+
findingGroups: findingGroups.length,
|
|
3538
|
+
agreements: agreements.length,
|
|
3539
|
+
conflicts: conflicts.length
|
|
3540
|
+
}
|
|
3541
|
+
};
|
|
3542
|
+
}
|
|
3543
|
+
function rankConfidence(confidence) {
|
|
3544
|
+
return confidence ? { low: 1, medium: 2, high: 3 }[confidence] : 0;
|
|
3545
|
+
}
|
|
3546
|
+
function detectConflicts(groups) {
|
|
3547
|
+
const conflicts = [];
|
|
3548
|
+
for (const group of groups) {
|
|
3549
|
+
if (group.sources.length < 2) continue;
|
|
3550
|
+
const severities = new Set(Object.values(group.severityBySource ?? {}));
|
|
3551
|
+
if (severities.size > 1) {
|
|
3552
|
+
conflicts.push({
|
|
3553
|
+
type: "severity",
|
|
3554
|
+
findingTitle: group.title,
|
|
3555
|
+
positions: group.sources.map((source) => position(source, group)),
|
|
3556
|
+
note: `Agents disagree on severity (${[...severities].sort((a, b) => SEVERITY_RANK2[a] - SEVERITY_RANK2[b]).join(", ")}); the highest severity is shown, every position is preserved.`
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
const recommendations = (group.recommendations ?? []).filter((r) => r.length > 0);
|
|
3560
|
+
if (recommendations.length > 1) {
|
|
3561
|
+
conflicts.push({
|
|
3562
|
+
type: "recommendation",
|
|
3563
|
+
findingTitle: group.title,
|
|
3564
|
+
positions: group.sources.map((source) => position(source, group)),
|
|
3565
|
+
note: `Agents proposed ${recommendations.length} different fixes; none was discarded.`
|
|
3566
|
+
});
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
return conflicts;
|
|
3570
|
+
}
|
|
3571
|
+
function position(source, group) {
|
|
3572
|
+
return {
|
|
3573
|
+
agent: source.agent,
|
|
3574
|
+
reportId: source.reportId,
|
|
3575
|
+
...group.severityBySource?.[source.agent] !== void 0 ? { severity: group.severityBySource[source.agent] } : {},
|
|
3576
|
+
...group.confidenceBySource?.[source.agent] !== void 0 ? { confidence: group.confidenceBySource[source.agent] } : {},
|
|
3577
|
+
...group.recommendations?.[0] !== void 0 ? { recommendation: group.recommendations[0] } : {}
|
|
2339
3578
|
};
|
|
2340
3579
|
}
|
|
2341
3580
|
function rank(finding) {
|
|
@@ -2348,10 +3587,22 @@ function mergeFinding(groups, finding, report, threshold) {
|
|
|
2348
3587
|
group.occurrences += 1;
|
|
2349
3588
|
group.details.push(finding.detail);
|
|
2350
3589
|
group.sources.push(source);
|
|
2351
|
-
group.evidence.push(...finding.evidence ?? []);
|
|
3590
|
+
group.evidence.push(...normalizeEvidence(finding.evidence) ?? []);
|
|
2352
3591
|
if (finding.severity && (!group.severity || SEVERITY_RANK2[finding.severity] > SEVERITY_RANK2[group.severity])) {
|
|
2353
3592
|
group.severity = finding.severity;
|
|
2354
3593
|
}
|
|
3594
|
+
if (finding.confidence && rankConfidence(finding.confidence) > rankConfidence(group.confidence)) {
|
|
3595
|
+
group.confidence = finding.confidence;
|
|
3596
|
+
}
|
|
3597
|
+
if (finding.severity) {
|
|
3598
|
+
group.severityBySource = { ...group.severityBySource, [report.agent]: finding.severity };
|
|
3599
|
+
}
|
|
3600
|
+
if (finding.confidence) {
|
|
3601
|
+
group.confidenceBySource = { ...group.confidenceBySource, [report.agent]: finding.confidence };
|
|
3602
|
+
}
|
|
3603
|
+
if (finding.recommendation && !group.recommendations?.includes(finding.recommendation)) {
|
|
3604
|
+
group.recommendations = [...group.recommendations ?? [], finding.recommendation];
|
|
3605
|
+
}
|
|
2355
3606
|
return;
|
|
2356
3607
|
}
|
|
2357
3608
|
groups.push({
|
|
@@ -2360,7 +3611,11 @@ function mergeFinding(groups, finding, report, threshold) {
|
|
|
2360
3611
|
sources: [source],
|
|
2361
3612
|
occurrences: 1,
|
|
2362
3613
|
...finding.severity ? { severity: finding.severity } : {},
|
|
2363
|
-
|
|
3614
|
+
...finding.confidence ? { confidence: finding.confidence } : {},
|
|
3615
|
+
...finding.severity ? { severityBySource: { [report.agent]: finding.severity } } : {},
|
|
3616
|
+
...finding.confidence ? { confidenceBySource: { [report.agent]: finding.confidence } } : {},
|
|
3617
|
+
...finding.recommendation ? { recommendations: [finding.recommendation] } : {},
|
|
3618
|
+
evidence: [...normalizeEvidence(finding.evidence) ?? []]
|
|
2364
3619
|
});
|
|
2365
3620
|
}
|
|
2366
3621
|
function mergeRecommendation(groups, text, report, threshold) {
|
|
@@ -2442,6 +3697,7 @@ function extractReportFromText(raw, meta) {
|
|
|
2442
3697
|
id: `${meta.taskId}-f-${index + 1}`,
|
|
2443
3698
|
title: truncate3(text, 120),
|
|
2444
3699
|
detail: text,
|
|
3700
|
+
description: text,
|
|
2445
3701
|
severity: guessSeverity(text)
|
|
2446
3702
|
}));
|
|
2447
3703
|
return {
|
|
@@ -2452,7 +3708,8 @@ function extractReportFromText(raw, meta) {
|
|
|
2452
3708
|
summary,
|
|
2453
3709
|
findings,
|
|
2454
3710
|
recommendations: recommendationTexts.map((t) => truncate3(t, 200)),
|
|
2455
|
-
|
|
3711
|
+
...evidenceTexts.length > 0 ? { evidence: evidenceTexts.map((excerpt) => ({ kind: "observation", excerpt })) } : {},
|
|
3712
|
+
metadata: evidenceTexts.length > 0 ? { evidenceCount: evidenceTexts.length } : void 0,
|
|
2456
3713
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2457
3714
|
};
|
|
2458
3715
|
}
|
|
@@ -2468,27 +3725,46 @@ var ReportService = class {
|
|
|
2468
3725
|
}
|
|
2469
3726
|
store;
|
|
2470
3727
|
log;
|
|
3728
|
+
/** Rejected reports from the last collection (§13 — malformed never enters aggregation). */
|
|
3729
|
+
lastDiagnostics = { collected: 0, accepted: 0, rejected: [] };
|
|
2471
3730
|
get storeRef() {
|
|
2472
3731
|
return this.store;
|
|
2473
3732
|
}
|
|
2474
|
-
/** Normalizes finished tasks into reports (§17 — normalizer)
|
|
3733
|
+
/** Normalizes finished tasks into reports (§17 — normalizer), validating each
|
|
3734
|
+
* one so malformed reports never reach aggregation (§13). */
|
|
2475
3735
|
async collectFromTasks(tasks) {
|
|
2476
|
-
const
|
|
3736
|
+
const candidates = [];
|
|
2477
3737
|
for (const task of tasks) {
|
|
2478
3738
|
const structured = task.result?.reports ?? [];
|
|
2479
3739
|
if (structured.length > 0) {
|
|
2480
|
-
|
|
3740
|
+
candidates.push(...structured);
|
|
2481
3741
|
continue;
|
|
2482
3742
|
}
|
|
2483
3743
|
const text = task.result?.rawOutput ?? task.result?.summary ?? "";
|
|
2484
3744
|
if (text.trim().length === 0) continue;
|
|
2485
|
-
|
|
3745
|
+
candidates.push(
|
|
2486
3746
|
extractReportFromText(text, {
|
|
2487
3747
|
taskId: task.id,
|
|
2488
3748
|
agent: task.agent ?? "unknown"
|
|
2489
3749
|
})
|
|
2490
3750
|
);
|
|
2491
3751
|
}
|
|
3752
|
+
const reports = [];
|
|
3753
|
+
const rejected = [];
|
|
3754
|
+
for (const candidate of candidates) {
|
|
3755
|
+
const result = validateReport(candidate);
|
|
3756
|
+
if (result.valid) {
|
|
3757
|
+
reports.push(result.report);
|
|
3758
|
+
} else {
|
|
3759
|
+
rejected.push({
|
|
3760
|
+
key: candidate?.id ?? "(unknown)",
|
|
3761
|
+
reason: result.reason,
|
|
3762
|
+
issues: result.issues.map((issue) => `${issue.path}: ${issue.message}`)
|
|
3763
|
+
});
|
|
3764
|
+
this.log.warn(`Rejected malformed report: ${result.reason}`);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
this.lastDiagnostics = { collected: candidates.length, accepted: reports.length, rejected };
|
|
2492
3768
|
return reports;
|
|
2493
3769
|
}
|
|
2494
3770
|
async saveReports(reports) {
|
|
@@ -2556,7 +3832,11 @@ function buildPipelineEngine(config, log = logger) {
|
|
|
2556
3832
|
agents,
|
|
2557
3833
|
roles,
|
|
2558
3834
|
store: new FileTaskStore(),
|
|
2559
|
-
...config.project.memory !== void 0 ? {
|
|
3835
|
+
...config.project.memory !== void 0 ? {
|
|
3836
|
+
memory: new MemoryService(createMemoryProvider(config.project.memory), log, {
|
|
3837
|
+
required: config.project.memory.required
|
|
3838
|
+
})
|
|
3839
|
+
} : {},
|
|
2560
3840
|
audit: new FileAuditLog(),
|
|
2561
3841
|
log
|
|
2562
3842
|
});
|
|
@@ -2594,12 +3874,14 @@ var ProjectStores = class {
|
|
|
2594
3874
|
};
|
|
2595
3875
|
|
|
2596
3876
|
// src/version.ts
|
|
2597
|
-
var OMNINODE_VERSION = "2.0.0-alpha.
|
|
3877
|
+
var OMNINODE_VERSION = "2.0.0-alpha.10";
|
|
2598
3878
|
export {
|
|
3879
|
+
AgentAdapterRegistry,
|
|
2599
3880
|
AgentError,
|
|
2600
3881
|
AgentRegistry,
|
|
2601
3882
|
ConfigError,
|
|
2602
3883
|
ConsoleLogSink,
|
|
3884
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
2603
3885
|
FileAuditLog,
|
|
2604
3886
|
FilePipelineRunStore,
|
|
2605
3887
|
FilePlanStore,
|
|
@@ -2609,6 +3891,8 @@ export {
|
|
|
2609
3891
|
JsonFileStore,
|
|
2610
3892
|
LocalMemoryProvider,
|
|
2611
3893
|
Logger,
|
|
3894
|
+
MAX_CONTEXT_CHARS,
|
|
3895
|
+
MAX_MESSAGE_BYTES,
|
|
2612
3896
|
MemoryError,
|
|
2613
3897
|
MemoryService,
|
|
2614
3898
|
ModelPlanner,
|
|
@@ -2618,16 +3902,22 @@ export {
|
|
|
2618
3902
|
OmniHilbrasProvider,
|
|
2619
3903
|
OmniNodeError,
|
|
2620
3904
|
OpenAICompatibleProvider,
|
|
3905
|
+
PROTOCOL_V2,
|
|
2621
3906
|
PipelineEngine,
|
|
2622
3907
|
PipelineError,
|
|
2623
3908
|
PlannerError,
|
|
2624
3909
|
ProcessAgent,
|
|
3910
|
+
ProcessAgentAdapter,
|
|
2625
3911
|
ProjectStores,
|
|
3912
|
+
ProtocolDecoder,
|
|
2626
3913
|
ProtocolError,
|
|
3914
|
+
ProtocolSession,
|
|
2627
3915
|
ProviderError,
|
|
2628
3916
|
RememberaMemoryProvider,
|
|
2629
3917
|
ReportService,
|
|
2630
3918
|
RoleRegistry,
|
|
3919
|
+
SUPPORTED_PROTOCOL_VERSIONS,
|
|
3920
|
+
TERMINAL_TASK_STATUSES,
|
|
2631
3921
|
TaskEngine,
|
|
2632
3922
|
TaskError,
|
|
2633
3923
|
agentConfigSchema,
|
|
@@ -2636,6 +3926,8 @@ export {
|
|
|
2636
3926
|
authHeaders,
|
|
2637
3927
|
buildAgentRegistry,
|
|
2638
3928
|
buildChildEnv,
|
|
3929
|
+
buildHello,
|
|
3930
|
+
buildHelloAck,
|
|
2639
3931
|
buildPipelineEngine,
|
|
2640
3932
|
buildPlanner,
|
|
2641
3933
|
buildPlannerContext,
|
|
@@ -2646,24 +3938,52 @@ export {
|
|
|
2646
3938
|
createMemoryProvider,
|
|
2647
3939
|
createProvider,
|
|
2648
3940
|
createTaskEngine,
|
|
3941
|
+
decodeMessage,
|
|
3942
|
+
decodeStream,
|
|
3943
|
+
defaultMetadata,
|
|
2649
3944
|
defaultProjectConfigYaml,
|
|
3945
|
+
defaultSearch,
|
|
3946
|
+
describeExit,
|
|
3947
|
+
detectConflicts,
|
|
3948
|
+
encodeMessage,
|
|
3949
|
+
envelope,
|
|
2650
3950
|
expandEnvRefs,
|
|
3951
|
+
extractMessage,
|
|
2651
3952
|
extractReportFromText,
|
|
2652
3953
|
findConfigFile,
|
|
3954
|
+
isNetworkError,
|
|
2653
3955
|
isOmniNodeError,
|
|
3956
|
+
isRetryable,
|
|
3957
|
+
isSupportedProtocol,
|
|
3958
|
+
isTimeoutError,
|
|
2654
3959
|
keywords,
|
|
3960
|
+
killTree,
|
|
3961
|
+
kindFromStatus,
|
|
2655
3962
|
loadConfig,
|
|
2656
3963
|
logger,
|
|
3964
|
+
newMessageId,
|
|
3965
|
+
normalizeEvidence,
|
|
3966
|
+
normalizeFinding,
|
|
3967
|
+
parseHello,
|
|
2657
3968
|
parsePlanJson,
|
|
3969
|
+
parseRetryAfter,
|
|
2658
3970
|
pipelineConfigSchema,
|
|
2659
3971
|
pipelineStepConfigSchema,
|
|
2660
3972
|
plannerConfigSchema,
|
|
2661
3973
|
projectConfigSchema,
|
|
2662
3974
|
providerConfigSchema,
|
|
3975
|
+
providerHttpError,
|
|
3976
|
+
providerTransportError,
|
|
2663
3977
|
registryKey,
|
|
2664
3978
|
requestJson,
|
|
3979
|
+
requireCompletion,
|
|
2665
3980
|
resolveApiKey,
|
|
3981
|
+
resolveEnvPolicy,
|
|
2666
3982
|
roleConfigSchema,
|
|
2667
|
-
|
|
3983
|
+
sanitizeChunk,
|
|
3984
|
+
similar,
|
|
3985
|
+
validateEnvelope,
|
|
3986
|
+
validateReport,
|
|
3987
|
+
validateWorkingDirectory
|
|
2668
3988
|
};
|
|
2669
3989
|
//# sourceMappingURL=index.js.map
|