@hilbras/omninode 1.0.0 → 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 +70 -1
- package/dist/cli/index.js +1696 -512
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +1037 -129
- package/dist/index.js +1717 -486
- 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.`);
|
|
@@ -987,40 +1816,54 @@ function buildRoleRegistry(config) {
|
|
|
987
1816
|
return registry;
|
|
988
1817
|
}
|
|
989
1818
|
|
|
990
|
-
// src/
|
|
1819
|
+
// src/persistence/json-file-store.ts
|
|
991
1820
|
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
992
1821
|
import { dirname } from "path";
|
|
993
|
-
var
|
|
1822
|
+
var JsonFileStore = class {
|
|
994
1823
|
filePath;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
*/
|
|
1824
|
+
schemaVersion;
|
|
1825
|
+
onCorrupt;
|
|
1826
|
+
log;
|
|
999
1827
|
queue = Promise.resolve();
|
|
1000
|
-
constructor(
|
|
1001
|
-
this.filePath = `${directory}
|
|
1828
|
+
constructor(options) {
|
|
1829
|
+
this.filePath = `${options.directory ?? `${process.cwd()}/.omninode`}/${options.fileName}`;
|
|
1830
|
+
this.schemaVersion = options.schemaVersion ?? 1;
|
|
1831
|
+
this.onCorrupt = options.onCorrupt;
|
|
1832
|
+
this.log = logger.child({ component: "persistence", file: options.fileName });
|
|
1002
1833
|
}
|
|
1003
|
-
|
|
1004
|
-
return this.
|
|
1005
|
-
const tasks = await this.load();
|
|
1006
|
-
const index = tasks.findIndex((t) => t.id === task.id);
|
|
1007
|
-
if (index >= 0) tasks[index] = task;
|
|
1008
|
-
else tasks.push(task);
|
|
1009
|
-
await this.write(tasks);
|
|
1010
|
-
});
|
|
1834
|
+
get path() {
|
|
1835
|
+
return this.filePath;
|
|
1011
1836
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1837
|
+
/** All items, or [] when missing/corrupt. */
|
|
1838
|
+
async readAll() {
|
|
1839
|
+
let raw;
|
|
1840
|
+
try {
|
|
1841
|
+
raw = await readFile(this.filePath, "utf8");
|
|
1842
|
+
} catch {
|
|
1843
|
+
return [];
|
|
1844
|
+
}
|
|
1845
|
+
try {
|
|
1846
|
+
const parsed = JSON.parse(raw);
|
|
1847
|
+
if (Array.isArray(parsed)) return parsed;
|
|
1848
|
+
if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.items)) {
|
|
1849
|
+
return parsed.items;
|
|
1850
|
+
}
|
|
1851
|
+
throw new Error("persisted file has an unrecognized shape");
|
|
1852
|
+
} catch (error) {
|
|
1853
|
+
this.log.warn(`Treating ${this.filePath} as empty \u2014 it could not be parsed.`);
|
|
1854
|
+
this.onCorrupt?.(this.filePath, error);
|
|
1855
|
+
return [];
|
|
1856
|
+
}
|
|
1017
1857
|
}
|
|
1018
|
-
async
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
}
|
|
1858
|
+
async writeAll(items) {
|
|
1859
|
+
const document = { schemaVersion: this.schemaVersion, items };
|
|
1860
|
+
const tmp = `${this.filePath}.tmp`;
|
|
1861
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
1862
|
+
await writeFile(tmp, `${JSON.stringify(document, null, 2)}
|
|
1863
|
+
`, "utf8");
|
|
1864
|
+
await rename(tmp, this.filePath);
|
|
1023
1865
|
}
|
|
1866
|
+
/** Serializes an async operation against this file. */
|
|
1024
1867
|
synchronized(operation) {
|
|
1025
1868
|
const result = this.queue.then(operation, operation);
|
|
1026
1869
|
this.queue = result.then(
|
|
@@ -1029,21 +1872,37 @@ var FileTaskStore = class {
|
|
|
1029
1872
|
);
|
|
1030
1873
|
return result;
|
|
1031
1874
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
return
|
|
1039
|
-
}
|
|
1875
|
+
/** Read-modify-write helper: applies `mutate` to the current items. */
|
|
1876
|
+
async mutate(mutate) {
|
|
1877
|
+
return this.synchronized(async () => {
|
|
1878
|
+
const items = await this.readAll();
|
|
1879
|
+
const [next, result] = await mutate(items);
|
|
1880
|
+
await this.writeAll(next);
|
|
1881
|
+
return result;
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
|
|
1886
|
+
// src/tasks/store.ts
|
|
1887
|
+
var FileTaskStore = class extends JsonFileStore {
|
|
1888
|
+
constructor(directory) {
|
|
1889
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "tasks.json" });
|
|
1890
|
+
}
|
|
1891
|
+
save(task) {
|
|
1892
|
+
return this.mutate((tasks) => {
|
|
1893
|
+
const index = tasks.findIndex((t) => t.id === task.id);
|
|
1894
|
+
if (index >= 0) tasks[index] = task;
|
|
1895
|
+
else tasks.push(task);
|
|
1896
|
+
return [tasks, void 0];
|
|
1897
|
+
});
|
|
1040
1898
|
}
|
|
1041
|
-
async
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
await
|
|
1899
|
+
async get(id) {
|
|
1900
|
+
const tasks = await this.readAll();
|
|
1901
|
+
return tasks.find((t) => t.id === id);
|
|
1902
|
+
}
|
|
1903
|
+
async list(filter = {}) {
|
|
1904
|
+
const tasks = await this.readAll();
|
|
1905
|
+
return tasks.filter((task) => filter.status ? task.status === filter.status : true).filter((task) => filter.project ? task.project === filter.project : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
1047
1906
|
}
|
|
1048
1907
|
};
|
|
1049
1908
|
|
|
@@ -1052,12 +1911,18 @@ import { randomBytes } from "crypto";
|
|
|
1052
1911
|
var ALLOWED_TRANSITIONS = {
|
|
1053
1912
|
created: ["queued", "running", "cancelled"],
|
|
1054
1913
|
queued: ["running", "cancelled"],
|
|
1055
|
-
running: ["completed", "failed", "cancelled"],
|
|
1914
|
+
running: ["completed", "failed", "timed_out", "unknown", "cancelled", "partially_completed"],
|
|
1056
1915
|
waiting: ["running", "cancelled"],
|
|
1057
1916
|
completed: [],
|
|
1058
|
-
failed:
|
|
1059
|
-
|
|
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: []
|
|
1060
1923
|
};
|
|
1924
|
+
var RETRYABLE_ERROR_CODES = ["AGENT_FAILED"];
|
|
1925
|
+
var EXECUTION_HISTORY_LIMIT = 10;
|
|
1061
1926
|
var TaskEngine = class {
|
|
1062
1927
|
constructor(options) {
|
|
1063
1928
|
this.options = options;
|
|
@@ -1089,6 +1954,7 @@ var TaskEngine = class {
|
|
|
1089
1954
|
id: generateTaskId(),
|
|
1090
1955
|
objective: input.objective.trim(),
|
|
1091
1956
|
status: "created",
|
|
1957
|
+
attempt: 0,
|
|
1092
1958
|
createdAt: now,
|
|
1093
1959
|
updatedAt: now,
|
|
1094
1960
|
...input.project !== void 0 ? { project: input.project } : {},
|
|
@@ -1105,8 +1971,10 @@ var TaskEngine = class {
|
|
|
1105
1971
|
const task = await this.mustGet(id);
|
|
1106
1972
|
return this.transition(task, "queued");
|
|
1107
1973
|
}
|
|
1108
|
-
/** Runs the task through its assigned agent and records the result.
|
|
1109
|
-
|
|
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 = {}) {
|
|
1110
1978
|
const task = await this.mustGet(id);
|
|
1111
1979
|
if (task.status !== "created" && task.status !== "queued") {
|
|
1112
1980
|
throw new TaskError("TASK_INVALID", `Task ${id} is "${task.status}" and cannot be run.`);
|
|
@@ -1119,49 +1987,89 @@ var TaskEngine = class {
|
|
|
1119
1987
|
throw new TaskError("TASK_INVALID", `Agent "${task.agent}" is not registered anymore.`);
|
|
1120
1988
|
}
|
|
1121
1989
|
const role = task.role ? this.options.roles.get(task.role) : void 0;
|
|
1122
|
-
const running = await this.transition(task, "running");
|
|
1123
1990
|
this.log.info(`Running task ${task.id} on agent "${task.agent}".`);
|
|
1124
|
-
let background =
|
|
1991
|
+
let background = task.context?.background;
|
|
1125
1992
|
if (this.options.memory) {
|
|
1126
1993
|
const memoryContext = await this.options.memory.gatherContext({
|
|
1127
|
-
objective:
|
|
1128
|
-
...
|
|
1129
|
-
...
|
|
1994
|
+
objective: task.objective,
|
|
1995
|
+
...task.role ? { role: task.role } : {},
|
|
1996
|
+
...task.project ? { project: task.project } : {}
|
|
1130
1997
|
});
|
|
1131
1998
|
if (memoryContext.length > 0) {
|
|
1132
1999
|
background = [memoryContext, background].filter((part) => part && part.length > 0).join("\n\n");
|
|
1133
2000
|
}
|
|
1134
2001
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
});
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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 }
|
|
1148
2065
|
});
|
|
2066
|
+
this.log.info(`Task ${current.id} finished: ${finalStatus}.`);
|
|
2067
|
+
break;
|
|
1149
2068
|
}
|
|
1150
|
-
const result = {
|
|
1151
|
-
...output.summary !== void 0 ? { summary: output.summary } : {},
|
|
1152
|
-
...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
|
|
1153
|
-
// Keep the verbatim output (bounded) for report extraction and audit.
|
|
1154
|
-
...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
|
|
1155
|
-
...output.error !== void 0 ? { error: output.error } : {},
|
|
1156
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1157
|
-
};
|
|
1158
|
-
const finalStatus = output.status === "completed" ? "completed" : "failed";
|
|
1159
|
-
const finished = await this.transition(running, finalStatus, { result });
|
|
1160
|
-
this.log.info(`Task ${task.id} finished: ${finalStatus}.`);
|
|
1161
2069
|
if (this.options.memory) {
|
|
1162
|
-
await this.options.memory.recordTaskOutcome(
|
|
2070
|
+
await this.options.memory.recordTaskOutcome(current);
|
|
1163
2071
|
}
|
|
1164
|
-
return
|
|
2072
|
+
return current;
|
|
1165
2073
|
}
|
|
1166
2074
|
async cancel(id) {
|
|
1167
2075
|
const task = await this.mustGet(id);
|
|
@@ -1174,13 +2082,14 @@ var TaskEngine = class {
|
|
|
1174
2082
|
}
|
|
1175
2083
|
return this.transition(task, "cancelled");
|
|
1176
2084
|
}
|
|
1177
|
-
/** Error recovery
|
|
2085
|
+
/** Error recovery: reset a failed/cancelled/unknown/timed_out task so it can run again. */
|
|
1178
2086
|
async retry(id) {
|
|
1179
2087
|
const task = await this.mustGet(id);
|
|
1180
|
-
|
|
2088
|
+
const retryableStates = ["failed", "cancelled", "unknown", "timed_out"];
|
|
2089
|
+
if (!retryableStates.includes(task.status)) {
|
|
1181
2090
|
throw new TaskError(
|
|
1182
2091
|
"TASK_INVALID",
|
|
1183
|
-
`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}".`
|
|
1184
2093
|
);
|
|
1185
2094
|
}
|
|
1186
2095
|
const { result: _discarded, ...rest } = task;
|
|
@@ -1212,10 +2121,14 @@ var TaskEngine = class {
|
|
|
1212
2121
|
if (!allowed.includes(to)) {
|
|
1213
2122
|
throw new TaskError("TASK_INVALID", `Task ${task.id} cannot move from "${task.status}" to "${to}".`);
|
|
1214
2123
|
}
|
|
2124
|
+
const executions = extra.execution ? [...task.executions ?? [], extra.execution].slice(-EXECUTION_HISTORY_LIMIT) : task.executions;
|
|
1215
2125
|
const updated = {
|
|
1216
2126
|
...task,
|
|
1217
2127
|
status: to,
|
|
1218
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 } : {},
|
|
1219
2132
|
...extra.result !== void 0 ? { result: { ...task.result, ...extra.result } } : {}
|
|
1220
2133
|
};
|
|
1221
2134
|
await this.options.store.save(updated);
|
|
@@ -1236,6 +2149,19 @@ var TaskEngine = class {
|
|
|
1236
2149
|
function generateTaskId() {
|
|
1237
2150
|
return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
1238
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
|
+
}
|
|
1239
2165
|
|
|
1240
2166
|
// src/audit/index.ts
|
|
1241
2167
|
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
@@ -1260,10 +2186,10 @@ var FileAuditLog = class {
|
|
|
1260
2186
|
}
|
|
1261
2187
|
/** Recent events, oldest first; used by the CLI. */
|
|
1262
2188
|
async recent(limit = 20) {
|
|
1263
|
-
const { readFile:
|
|
2189
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
1264
2190
|
let raw;
|
|
1265
2191
|
try {
|
|
1266
|
-
raw = await
|
|
2192
|
+
raw = await readFile2(this.filePath, "utf8");
|
|
1267
2193
|
} catch {
|
|
1268
2194
|
return [];
|
|
1269
2195
|
}
|
|
@@ -1278,9 +2204,24 @@ var FileAuditLog = class {
|
|
|
1278
2204
|
}
|
|
1279
2205
|
};
|
|
1280
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
|
+
|
|
1281
2224
|
// src/memory/local.ts
|
|
1282
|
-
import { mkdir as mkdir3, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
1283
|
-
import { dirname as dirname3 } from "path";
|
|
1284
2225
|
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
1285
2226
|
"the",
|
|
1286
2227
|
"and",
|
|
@@ -1337,29 +2278,42 @@ function relevanceScore(queryKeywords, entry) {
|
|
|
1337
2278
|
}
|
|
1338
2279
|
return overlap * 2 + tagMatches * 1.5;
|
|
1339
2280
|
}
|
|
1340
|
-
var LocalMemoryProvider = class {
|
|
2281
|
+
var LocalMemoryProvider = class extends JsonFileStore {
|
|
1341
2282
|
name = "local";
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
const filtered = entries.filter((entry) => query.scope ? entry.scope === query.scope : true).filter(
|
|
1351
|
-
(entry) => query.tags && query.tags.length > 0 ? query.tags.every((tag) => (entry.tags ?? []).includes(tag)) : true
|
|
1352
|
-
);
|
|
1353
|
-
if (!query.text || query.text.trim().length === 0) {
|
|
1354
|
-
return filtered.sort((a, b) => (b.updatedAt ?? b.createdAt ?? "").localeCompare(a.updatedAt ?? a.createdAt ?? "")).slice(0, query.limit ?? 10);
|
|
1355
|
-
}
|
|
1356
|
-
const queryKeywords = keywords(query.text);
|
|
1357
|
-
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);
|
|
2283
|
+
constructor(directory) {
|
|
2284
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "memory.json" });
|
|
2285
|
+
}
|
|
2286
|
+
metadata() {
|
|
2287
|
+
return defaultMetadata(this.name, {
|
|
2288
|
+
capabilities: ["retrieve", "store", "search", "categories", "tags"],
|
|
2289
|
+
relevanceRanking: true,
|
|
2290
|
+
backend: { kind: "json-file", deterministicScoring: true }
|
|
1358
2291
|
});
|
|
1359
2292
|
}
|
|
1360
|
-
|
|
1361
|
-
return this
|
|
1362
|
-
|
|
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) {
|
|
2305
|
+
const entries = await this.readAll();
|
|
2306
|
+
const filtered = entries.filter((entry) => query.scope ? entry.scope === query.scope : true).filter((entry) => query.category ? entry.category === query.category : true).filter(
|
|
2307
|
+
(entry) => query.tags && query.tags.length > 0 ? query.tags.every((tag) => (entry.tags ?? []).includes(tag)) : true
|
|
2308
|
+
);
|
|
2309
|
+
if (!query.text || query.text.trim().length === 0) {
|
|
2310
|
+
return filtered.sort((a, b) => (b.updatedAt ?? b.createdAt ?? "").localeCompare(a.updatedAt ?? a.createdAt ?? "")).slice(0, query.limit ?? 10);
|
|
2311
|
+
}
|
|
2312
|
+
const queryKeywords = keywords(query.text);
|
|
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);
|
|
2314
|
+
}
|
|
2315
|
+
store(entry) {
|
|
2316
|
+
return this.mutate((entries) => {
|
|
1363
2317
|
const index = entries.findIndex((e) => e.key === entry.key);
|
|
1364
2318
|
const stamped = {
|
|
1365
2319
|
...entry,
|
|
@@ -1368,35 +2322,11 @@ var LocalMemoryProvider = class {
|
|
|
1368
2322
|
};
|
|
1369
2323
|
if (index >= 0) entries[index] = stamped;
|
|
1370
2324
|
else entries.push(stamped);
|
|
1371
|
-
|
|
2325
|
+
return [entries, void 0];
|
|
1372
2326
|
});
|
|
1373
2327
|
}
|
|
1374
2328
|
async count() {
|
|
1375
|
-
return (await this.
|
|
1376
|
-
}
|
|
1377
|
-
synchronized(operation) {
|
|
1378
|
-
const result = this.queue.then(operation, operation);
|
|
1379
|
-
this.queue = result.then(
|
|
1380
|
-
() => void 0,
|
|
1381
|
-
() => void 0
|
|
1382
|
-
);
|
|
1383
|
-
return result;
|
|
1384
|
-
}
|
|
1385
|
-
async load() {
|
|
1386
|
-
try {
|
|
1387
|
-
const raw = await readFile2(this.filePath, "utf8");
|
|
1388
|
-
const parsed = JSON.parse(raw);
|
|
1389
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
1390
|
-
} catch {
|
|
1391
|
-
return [];
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
async saveAll(entries) {
|
|
1395
|
-
const tmp = `${this.filePath}.tmp`;
|
|
1396
|
-
await mkdir3(dirname3(this.filePath), { recursive: true });
|
|
1397
|
-
await writeFile2(tmp, `${JSON.stringify(entries, null, 2)}
|
|
1398
|
-
`, "utf8");
|
|
1399
|
-
await rename2(tmp, this.filePath);
|
|
2329
|
+
return (await this.readAll()).length;
|
|
1400
2330
|
}
|
|
1401
2331
|
};
|
|
1402
2332
|
|
|
@@ -1413,11 +2343,40 @@ var RememberaMemoryProvider = class {
|
|
|
1413
2343
|
this.env = config.env ?? process.env;
|
|
1414
2344
|
this.log = log.child({ component: "remembera" });
|
|
1415
2345
|
}
|
|
2346
|
+
get authenticationConfigured() {
|
|
2347
|
+
return this.apiKeyEnvVar === void 0 || Boolean(this.env[this.apiKeyEnvVar]);
|
|
2348
|
+
}
|
|
1416
2349
|
headers() {
|
|
1417
2350
|
const key = this.apiKeyEnvVar ? resolveApiKey({ name: "remembera", apiKeyEnvVar: this.apiKeyEnvVar }, this.env) : void 0;
|
|
1418
2351
|
return key ? { authorization: `Bearer ${key}` } : {};
|
|
1419
2352
|
}
|
|
1420
|
-
|
|
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) {
|
|
1421
2380
|
const { status, body } = await this.request(`${this.baseUrl}/api/memory/query`, query);
|
|
1422
2381
|
if (status !== 200) {
|
|
1423
2382
|
throw new MemoryError(`Remembera query returned HTTP ${status}.`);
|
|
@@ -1428,7 +2387,7 @@ var RememberaMemoryProvider = class {
|
|
|
1428
2387
|
}
|
|
1429
2388
|
return entries.map((entry) => this.toEntry(entry)).filter((entry) => entry !== void 0);
|
|
1430
2389
|
}
|
|
1431
|
-
async
|
|
2390
|
+
async store(entry) {
|
|
1432
2391
|
const { status } = await this.request(`${this.baseUrl}/api/memory`, entry);
|
|
1433
2392
|
if (status !== 200 && status !== 201) {
|
|
1434
2393
|
throw new MemoryError(`Remembera write returned HTTP ${status}.`);
|
|
@@ -1463,6 +2422,7 @@ var RememberaMemoryProvider = class {
|
|
|
1463
2422
|
content: entry.content,
|
|
1464
2423
|
scope: scope === "project" || scope === "role" || scope === "task" || scope === "knowledge" ? scope : "knowledge",
|
|
1465
2424
|
...Array.isArray(entry.tags) ? { tags: entry.tags.filter((t) => typeof t === "string") } : {},
|
|
2425
|
+
...typeof entry.category === "string" ? { category: entry.category } : {},
|
|
1466
2426
|
...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
|
|
1467
2427
|
...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {}
|
|
1468
2428
|
};
|
|
@@ -1493,57 +2453,54 @@ function createMemoryProvider(config) {
|
|
|
1493
2453
|
}
|
|
1494
2454
|
|
|
1495
2455
|
// src/memory/service.ts
|
|
2456
|
+
var DEFAULT_ENTRY_LIMIT = 8;
|
|
1496
2457
|
var MAX_CONTEXT_CHARS = 4e3;
|
|
1497
2458
|
var MemoryService = class {
|
|
1498
|
-
constructor(provider, log = logger) {
|
|
2459
|
+
constructor(provider, log = logger, options = {}) {
|
|
1499
2460
|
this.provider = provider;
|
|
1500
2461
|
this.log = log.child({ component: "memory", provider: provider.name });
|
|
2462
|
+
this.required = options.required ?? false;
|
|
2463
|
+
this.maxContextChars = options.maxContextChars ?? MAX_CONTEXT_CHARS;
|
|
1501
2464
|
}
|
|
1502
2465
|
provider;
|
|
1503
2466
|
log;
|
|
2467
|
+
required;
|
|
2468
|
+
maxContextChars;
|
|
1504
2469
|
get providerRef() {
|
|
1505
2470
|
return this.provider;
|
|
1506
2471
|
}
|
|
1507
2472
|
get providerName() {
|
|
1508
2473
|
return this.provider.name;
|
|
1509
2474
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
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;
|
|
1512
2481
|
try {
|
|
1513
|
-
|
|
2482
|
+
entries = await this.provider.retrieve({
|
|
1514
2483
|
text: query.objective,
|
|
1515
2484
|
...query.role ? { tags: [query.role] } : {},
|
|
1516
|
-
limit:
|
|
2485
|
+
limit: query.limit ?? DEFAULT_ENTRY_LIMIT
|
|
1517
2486
|
});
|
|
1518
|
-
if (entries.length === 0) return "";
|
|
1519
|
-
const byScope = /* @__PURE__ */ new Map();
|
|
1520
|
-
for (const entry of entries) {
|
|
1521
|
-
byScope.set(entry.scope, [...byScope.get(entry.scope) ?? [], entry]);
|
|
1522
|
-
}
|
|
1523
|
-
const parts = [];
|
|
1524
|
-
for (const scope of ["project", "task", "role", "knowledge"]) {
|
|
1525
|
-
const scopeEntries = byScope.get(scope) ?? [];
|
|
1526
|
-
if (scopeEntries.length === 0) continue;
|
|
1527
|
-
const lines = scopeEntries.map((entry) => `- ${entry.content}`).join("\n");
|
|
1528
|
-
parts.push(`### Relevant memory (${scope})
|
|
1529
|
-
${lines}`);
|
|
1530
|
-
}
|
|
1531
|
-
const context = parts.join("\n\n");
|
|
1532
|
-
return context.length > MAX_CONTEXT_CHARS ? `${context.slice(0, MAX_CONTEXT_CHARS)}\u2026` : context;
|
|
1533
2487
|
} catch (error) {
|
|
1534
|
-
this.
|
|
1535
|
-
`Memory query failed (continuing without memory): ${error instanceof Error ? error.message : String(error)}`
|
|
1536
|
-
);
|
|
1537
|
-
return "";
|
|
2488
|
+
return this.handleFailure("retrieve", error, { text: "", entries: [], truncated: false });
|
|
1538
2489
|
}
|
|
2490
|
+
return this.format(entries);
|
|
2491
|
+
}
|
|
2492
|
+
/** Prompt-ready context text (v1 convenience wrapper). */
|
|
2493
|
+
async gatherContext(query) {
|
|
2494
|
+
return (await this.contextFor(query)).text;
|
|
1539
2495
|
}
|
|
1540
|
-
/** Records a finished task's outcome —
|
|
2496
|
+
/** Records a finished task's outcome — task history plus "known problems" (§12). */
|
|
1541
2497
|
async recordTaskOutcome(task) {
|
|
1542
2498
|
const outcome = task.result?.summary ?? task.result?.error ?? "(no output)";
|
|
1543
2499
|
try {
|
|
1544
|
-
await this.provider.
|
|
2500
|
+
await this.provider.store({
|
|
1545
2501
|
key: `task:${task.id}`,
|
|
1546
2502
|
scope: "task",
|
|
2503
|
+
category: "tasks",
|
|
1547
2504
|
content: `Objective: ${task.objective}
|
|
1548
2505
|
Status: ${task.status}
|
|
1549
2506
|
Outcome: ${outcome}`,
|
|
@@ -1554,18 +2511,53 @@ Outcome: ${outcome}`,
|
|
|
1554
2511
|
(report) => report.findings.filter((finding) => finding.severity === "high" || finding.severity === "critical").map((finding) => ({ report, finding }))
|
|
1555
2512
|
);
|
|
1556
2513
|
for (const { report, finding } of significant) {
|
|
1557
|
-
await this.provider.
|
|
2514
|
+
await this.provider.store({
|
|
1558
2515
|
key: `finding:${report.id}:${finding.id}`,
|
|
1559
2516
|
scope: "project",
|
|
2517
|
+
category: "problems",
|
|
1560
2518
|
content: `[${finding.severity}] ${finding.title} \u2014 ${finding.detail}`,
|
|
1561
2519
|
tags: ["known-problem", report.agent]
|
|
1562
2520
|
});
|
|
1563
2521
|
}
|
|
1564
2522
|
} catch (error) {
|
|
1565
|
-
this.
|
|
1566
|
-
|
|
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 }
|
|
1567
2557
|
);
|
|
1568
2558
|
}
|
|
2559
|
+
this.log.warn(`Memory ${operation} failed (continuing without memory): ${message}`);
|
|
2560
|
+
return fallback ?? { text: "", entries: [], truncated: false };
|
|
1569
2561
|
}
|
|
1570
2562
|
};
|
|
1571
2563
|
|
|
@@ -1577,7 +2569,11 @@ function createTaskEngine(config, log = logger, directory) {
|
|
|
1577
2569
|
agents,
|
|
1578
2570
|
roles,
|
|
1579
2571
|
store: new FileTaskStore(directory),
|
|
1580
|
-
...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
|
+
} : {},
|
|
1581
2577
|
audit: new FileAuditLog(),
|
|
1582
2578
|
log
|
|
1583
2579
|
});
|
|
@@ -1816,9 +2812,23 @@ var PipelineEngine = class {
|
|
|
1816
2812
|
}
|
|
1817
2813
|
options;
|
|
1818
2814
|
log;
|
|
2815
|
+
/** Runs executing in THIS process — cancellation can terminate them directly. */
|
|
2816
|
+
activeRuns = /* @__PURE__ */ new Map();
|
|
1819
2817
|
get store() {
|
|
1820
2818
|
return this.options.store;
|
|
1821
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
|
+
}
|
|
1822
2832
|
/** Structural + semantic validation (§27 Phase 5 — pipeline parser). */
|
|
1823
2833
|
validate(def) {
|
|
1824
2834
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -1918,18 +2928,44 @@ var PipelineEngine = class {
|
|
|
1918
2928
|
);
|
|
1919
2929
|
}
|
|
1920
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
|
+
}
|
|
1921
2947
|
}
|
|
1922
2948
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1923
2949
|
const run = {
|
|
1924
2950
|
id: `run-${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`,
|
|
1925
2951
|
pipelineId: def.id,
|
|
2952
|
+
attempt: 1,
|
|
1926
2953
|
objective,
|
|
1927
2954
|
status: "running",
|
|
1928
2955
|
startedAt: now,
|
|
1929
2956
|
stepRuns: []
|
|
1930
2957
|
};
|
|
1931
2958
|
await this.options.store.save(run);
|
|
1932
|
-
|
|
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}).`);
|
|
1933
2969
|
await this.options.audit?.record({
|
|
1934
2970
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1935
2971
|
action: "pipeline.run.started",
|
|
@@ -1941,7 +2977,22 @@ var PipelineEngine = class {
|
|
|
1941
2977
|
const pending = new Set(def.steps.map((s) => s.id));
|
|
1942
2978
|
const contextParts = [];
|
|
1943
2979
|
const allReports = [];
|
|
2980
|
+
let cancelled = false;
|
|
1944
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
|
+
}
|
|
1945
2996
|
const ready = def.steps.filter(
|
|
1946
2997
|
(step) => pending.has(step.id) && (deps.get(step.id) ?? []).every((d) => outcomes.has(d))
|
|
1947
2998
|
);
|
|
@@ -1958,7 +3009,8 @@ var PipelineEngine = class {
|
|
|
1958
3009
|
...role ? { role } : {},
|
|
1959
3010
|
combinedContext: contextParts.join("\n\n"),
|
|
1960
3011
|
reports: [...allReports],
|
|
1961
|
-
pipelineRunId: run.id
|
|
3012
|
+
pipelineRunId: run.id,
|
|
3013
|
+
runId: run.id
|
|
1962
3014
|
};
|
|
1963
3015
|
return this.executeStep(step, ctx);
|
|
1964
3016
|
})
|
|
@@ -1975,7 +3027,9 @@ ${contribution}`);
|
|
|
1975
3027
|
}
|
|
1976
3028
|
const stepRun = {
|
|
1977
3029
|
stepId: step.id,
|
|
1978
|
-
|
|
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",
|
|
1979
3033
|
...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
|
|
1980
3034
|
...outcome.planId !== void 0 ? { planId: outcome.planId } : {},
|
|
1981
3035
|
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
@@ -1984,10 +3038,12 @@ ${contribution}`);
|
|
|
1984
3038
|
options.onStep?.(stepRun);
|
|
1985
3039
|
});
|
|
1986
3040
|
run.reports = [...allReports];
|
|
1987
|
-
await this.
|
|
3041
|
+
await this.persist(run);
|
|
1988
3042
|
}
|
|
1989
|
-
const
|
|
1990
|
-
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";
|
|
1991
3047
|
const planId = [...outcomes.values()].find((o) => o.planId !== void 0)?.planId;
|
|
1992
3048
|
let resultSummary;
|
|
1993
3049
|
for (let i = def.steps.length - 1; i >= 0; i -= 1) {
|
|
@@ -2015,16 +3071,19 @@ ${contribution}`);
|
|
|
2015
3071
|
run.reports = collected;
|
|
2016
3072
|
}
|
|
2017
3073
|
}
|
|
3074
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3075
|
+
this.activeRuns.delete(run.id);
|
|
2018
3076
|
const finished = {
|
|
2019
3077
|
...run,
|
|
2020
3078
|
status: finalStatus,
|
|
2021
|
-
finishedAt
|
|
3079
|
+
finishedAt,
|
|
3080
|
+
attempts: [...run.attempts ?? [], { attempt: run.attempt, startedAt: run.startedAt ?? finishedAt, finishedAt, status: finalStatus }],
|
|
2022
3081
|
...run.reports !== void 0 && run.reports.length > 0 ? { reports: run.reports } : {},
|
|
2023
3082
|
...combinedReportId !== void 0 ? { combinedReportId } : {},
|
|
2024
3083
|
...planId !== void 0 ? { planId } : {},
|
|
2025
3084
|
...resultSummary !== void 0 ? { resultSummary } : {}
|
|
2026
3085
|
};
|
|
2027
|
-
await this.
|
|
3086
|
+
await this.persist(finished);
|
|
2028
3087
|
this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
|
|
2029
3088
|
await this.options.audit?.record({
|
|
2030
3089
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2034,6 +3093,23 @@ ${contribution}`);
|
|
|
2034
3093
|
});
|
|
2035
3094
|
return finished;
|
|
2036
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
|
+
}
|
|
2037
3113
|
async get(runId) {
|
|
2038
3114
|
return this.options.store.get(runId);
|
|
2039
3115
|
}
|
|
@@ -2057,9 +3133,16 @@ ${contribution}`);
|
|
|
2057
3133
|
this.log.warn(`Step "${step.id}" failed \u2014 retry ${attempt}/${retries}.`);
|
|
2058
3134
|
}
|
|
2059
3135
|
last = await this.executeStepOnce(step, ctx);
|
|
2060
|
-
if (last.status !== "failed") return last;
|
|
3136
|
+
if (last.status !== "failed") return { ...last, attempt: attempt + 1 };
|
|
2061
3137
|
}
|
|
2062
|
-
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);
|
|
2063
3146
|
}
|
|
2064
3147
|
async executeStepOnce(step, ctx) {
|
|
2065
3148
|
switch (step.kind) {
|
|
@@ -2092,7 +3175,10 @@ ${contribution}`);
|
|
|
2092
3175
|
})
|
|
2093
3176
|
)
|
|
2094
3177
|
);
|
|
3178
|
+
this.trackLiveTasks(ctx.runId, created.map((t) => t.id));
|
|
2095
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");
|
|
2096
3182
|
const failedTasks = results.filter((r) => r.status !== "completed");
|
|
2097
3183
|
const lines = results.map((result, index) => {
|
|
2098
3184
|
const agentName = agentNames[index] ?? "unknown";
|
|
@@ -2102,16 +3188,25 @@ ${contribution}`);
|
|
|
2102
3188
|
const reports = results.flatMap((r) => r.result?.reports ?? []);
|
|
2103
3189
|
const summary = lines.join("\n");
|
|
2104
3190
|
if (failedTasks.length > 0) {
|
|
2105
|
-
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(", ");
|
|
2106
3193
|
return {
|
|
2107
|
-
|
|
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",
|
|
2108
3197
|
summary,
|
|
2109
3198
|
taskIds: created.map((t) => t.id),
|
|
2110
|
-
error: `${failedTasks.length}/${results.length} task(s)
|
|
3199
|
+
error: `${failedTasks.length}/${results.length} task(s) incomplete (${states}) \u2014 ${firstError}`,
|
|
2111
3200
|
reports
|
|
2112
3201
|
};
|
|
2113
3202
|
}
|
|
2114
|
-
return {
|
|
3203
|
+
return {
|
|
3204
|
+
status: "completed",
|
|
3205
|
+
summary,
|
|
3206
|
+
taskIds: created.map((t) => t.id),
|
|
3207
|
+
...executionIds.length > 0 ? { executionIds } : {},
|
|
3208
|
+
reports
|
|
3209
|
+
};
|
|
2115
3210
|
}
|
|
2116
3211
|
async runSingle(step, ctx, agentName) {
|
|
2117
3212
|
const task = await this.options.tasks.create({
|
|
@@ -2120,10 +3215,13 @@ ${contribution}`);
|
|
|
2120
3215
|
...ctx.role ? { role: ctx.role.id } : {},
|
|
2121
3216
|
...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
|
|
2122
3217
|
});
|
|
3218
|
+
this.trackLiveTasks(ctx.runId, [task.id]);
|
|
2123
3219
|
const result = await this.options.tasks.run(task.id);
|
|
2124
3220
|
if (result.status !== "completed") {
|
|
2125
3221
|
return {
|
|
2126
|
-
|
|
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",
|
|
2127
3225
|
summary: result.result?.summary ?? "",
|
|
2128
3226
|
taskIds: [task.id],
|
|
2129
3227
|
error: result.result?.error ?? `task ${task.id} did not complete`,
|
|
@@ -2239,114 +3337,44 @@ function createDefaultChatFn(providers) {
|
|
|
2239
3337
|
}
|
|
2240
3338
|
|
|
2241
3339
|
// src/pipelines/store.ts
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
filePath;
|
|
2246
|
-
queue = Promise.resolve();
|
|
2247
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
2248
|
-
this.filePath = `${directory}/pipelines.json`;
|
|
3340
|
+
var FilePipelineRunStore = class extends JsonFileStore {
|
|
3341
|
+
constructor(directory) {
|
|
3342
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "pipelines.json" });
|
|
2249
3343
|
}
|
|
2250
|
-
|
|
2251
|
-
return this.
|
|
2252
|
-
const runs = await this.load();
|
|
3344
|
+
save(run) {
|
|
3345
|
+
return this.mutate((runs) => {
|
|
2253
3346
|
const index = runs.findIndex((r) => r.id === run.id);
|
|
2254
3347
|
if (index >= 0) runs[index] = run;
|
|
2255
3348
|
else runs.push(run);
|
|
2256
|
-
|
|
3349
|
+
return [runs, void 0];
|
|
2257
3350
|
});
|
|
2258
3351
|
}
|
|
2259
3352
|
async get(id) {
|
|
2260
|
-
return this.
|
|
2261
|
-
const runs = await this.load();
|
|
2262
|
-
return runs.find((r) => r.id === id);
|
|
2263
|
-
});
|
|
3353
|
+
return (await this.readAll()).find((r) => r.id === id);
|
|
2264
3354
|
}
|
|
2265
3355
|
async list(filter = {}) {
|
|
2266
|
-
return this.
|
|
2267
|
-
const runs = await this.load();
|
|
2268
|
-
return runs.filter((run) => filter.pipelineId ? run.pipelineId === filter.pipelineId : true).filter((run) => filter.status ? run.status === filter.status : true).sort((a, b) => (a.startedAt ?? "").localeCompare(b.startedAt ?? ""));
|
|
2269
|
-
});
|
|
2270
|
-
}
|
|
2271
|
-
synchronized(operation) {
|
|
2272
|
-
const result = this.queue.then(operation, operation);
|
|
2273
|
-
this.queue = result.then(
|
|
2274
|
-
() => void 0,
|
|
2275
|
-
() => void 0
|
|
2276
|
-
);
|
|
2277
|
-
return result;
|
|
2278
|
-
}
|
|
2279
|
-
async load() {
|
|
2280
|
-
try {
|
|
2281
|
-
const raw = await readFile3(this.filePath, "utf8");
|
|
2282
|
-
const parsed = JSON.parse(raw);
|
|
2283
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
2284
|
-
} catch {
|
|
2285
|
-
return [];
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
async write(runs) {
|
|
2289
|
-
const tmp = `${this.filePath}.tmp`;
|
|
2290
|
-
await mkdir4(dirname4(this.filePath), { recursive: true });
|
|
2291
|
-
await writeFile3(tmp, `${JSON.stringify(runs, null, 2)}
|
|
2292
|
-
`, "utf8");
|
|
2293
|
-
await rename3(tmp, this.filePath);
|
|
3356
|
+
return (await this.readAll()).filter((run) => filter.pipelineId ? run.pipelineId === filter.pipelineId : true).filter((run) => filter.status ? run.status === filter.status : true).sort((a, b) => (a.startedAt ?? "").localeCompare(b.startedAt ?? ""));
|
|
2294
3357
|
}
|
|
2295
3358
|
};
|
|
2296
3359
|
|
|
2297
3360
|
// src/planner/store.ts
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
filePath;
|
|
2302
|
-
queue = Promise.resolve();
|
|
2303
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
2304
|
-
this.filePath = `${directory}/plans.json`;
|
|
3361
|
+
var FilePlanStore = class extends JsonFileStore {
|
|
3362
|
+
constructor(directory) {
|
|
3363
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "plans.json" });
|
|
2305
3364
|
}
|
|
2306
|
-
|
|
2307
|
-
return this.
|
|
2308
|
-
const plans = await this.load();
|
|
3365
|
+
save(plan) {
|
|
3366
|
+
return this.mutate((plans) => {
|
|
2309
3367
|
const index = plans.findIndex((p) => p.id === plan.id);
|
|
2310
3368
|
if (index >= 0) plans[index] = plan;
|
|
2311
3369
|
else plans.push(plan);
|
|
2312
|
-
|
|
3370
|
+
return [plans, void 0];
|
|
2313
3371
|
});
|
|
2314
3372
|
}
|
|
2315
3373
|
async get(id) {
|
|
2316
|
-
return this.
|
|
2317
|
-
const plans = await this.load();
|
|
2318
|
-
return plans.find((p) => p.id === id);
|
|
2319
|
-
});
|
|
3374
|
+
return (await this.readAll()).find((p) => p.id === id);
|
|
2320
3375
|
}
|
|
2321
3376
|
async list(filter = {}) {
|
|
2322
|
-
return this.
|
|
2323
|
-
const plans = await this.load();
|
|
2324
|
-
return plans.filter((plan) => filter.pipelineRunId ? plan.pipelineRunId === filter.pipelineRunId : true).filter((plan) => filter.taskId ? plan.taskId === filter.taskId : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2325
|
-
});
|
|
2326
|
-
}
|
|
2327
|
-
synchronized(operation) {
|
|
2328
|
-
const result = this.queue.then(operation, operation);
|
|
2329
|
-
this.queue = result.then(
|
|
2330
|
-
() => void 0,
|
|
2331
|
-
() => void 0
|
|
2332
|
-
);
|
|
2333
|
-
return result;
|
|
2334
|
-
}
|
|
2335
|
-
async load() {
|
|
2336
|
-
try {
|
|
2337
|
-
const raw = await readFile4(this.filePath, "utf8");
|
|
2338
|
-
const parsed = JSON.parse(raw);
|
|
2339
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
2340
|
-
} catch {
|
|
2341
|
-
return [];
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
async write(plans) {
|
|
2345
|
-
const tmp = `${this.filePath}.tmp`;
|
|
2346
|
-
await mkdir5(dirname5(this.filePath), { recursive: true });
|
|
2347
|
-
await writeFile4(tmp, `${JSON.stringify(plans, null, 2)}
|
|
2348
|
-
`, "utf8");
|
|
2349
|
-
await rename4(tmp, this.filePath);
|
|
3377
|
+
return (await this.readAll()).filter((plan) => filter.pipelineRunId ? plan.pipelineRunId === filter.pipelineRunId : true).filter((plan) => filter.taskId ? plan.taskId === filter.taskId : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2350
3378
|
}
|
|
2351
3379
|
};
|
|
2352
3380
|
|
|
@@ -2373,6 +3401,94 @@ function buildPlanner(plannerConfig, chat, log = logger) {
|
|
|
2373
3401
|
|
|
2374
3402
|
// src/reports/aggregate.ts
|
|
2375
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
|
|
2376
3492
|
var SEVERITY_RANK2 = {
|
|
2377
3493
|
info: 0,
|
|
2378
3494
|
low: 1,
|
|
@@ -2394,7 +3510,12 @@ function aggregateReports(reports, options = {}) {
|
|
|
2394
3510
|
}
|
|
2395
3511
|
const sources = [...new Set(reports.map((r) => r.agent))].sort();
|
|
2396
3512
|
const totalRawFindings = reports.reduce((sum, r) => sum + r.findings.length, 0);
|
|
2397
|
-
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).`;
|
|
2398
3519
|
return {
|
|
2399
3520
|
id: `combined-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`,
|
|
2400
3521
|
title: options.objective !== void 0 ? truncate2(options.objective, 120) : "Combined report",
|
|
@@ -2403,10 +3524,57 @@ function aggregateReports(reports, options = {}) {
|
|
|
2403
3524
|
taskIds: options.taskIds ?? [],
|
|
2404
3525
|
summary,
|
|
2405
3526
|
findings: findingGroups.sort((a, b) => rank(b) - rank(a)),
|
|
3527
|
+
agreements,
|
|
3528
|
+
conflicts,
|
|
2406
3529
|
recommendations: recommendationGroups,
|
|
2407
3530
|
sources,
|
|
2408
3531
|
reportIds: reports.map((r) => r.id),
|
|
2409
|
-
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] } : {}
|
|
2410
3578
|
};
|
|
2411
3579
|
}
|
|
2412
3580
|
function rank(finding) {
|
|
@@ -2419,10 +3587,22 @@ function mergeFinding(groups, finding, report, threshold) {
|
|
|
2419
3587
|
group.occurrences += 1;
|
|
2420
3588
|
group.details.push(finding.detail);
|
|
2421
3589
|
group.sources.push(source);
|
|
2422
|
-
group.evidence.push(...finding.evidence ?? []);
|
|
3590
|
+
group.evidence.push(...normalizeEvidence(finding.evidence) ?? []);
|
|
2423
3591
|
if (finding.severity && (!group.severity || SEVERITY_RANK2[finding.severity] > SEVERITY_RANK2[group.severity])) {
|
|
2424
3592
|
group.severity = finding.severity;
|
|
2425
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
|
+
}
|
|
2426
3606
|
return;
|
|
2427
3607
|
}
|
|
2428
3608
|
groups.push({
|
|
@@ -2431,7 +3611,11 @@ function mergeFinding(groups, finding, report, threshold) {
|
|
|
2431
3611
|
sources: [source],
|
|
2432
3612
|
occurrences: 1,
|
|
2433
3613
|
...finding.severity ? { severity: finding.severity } : {},
|
|
2434
|
-
|
|
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) ?? []]
|
|
2435
3619
|
});
|
|
2436
3620
|
}
|
|
2437
3621
|
function mergeRecommendation(groups, text, report, threshold) {
|
|
@@ -2513,6 +3697,7 @@ function extractReportFromText(raw, meta) {
|
|
|
2513
3697
|
id: `${meta.taskId}-f-${index + 1}`,
|
|
2514
3698
|
title: truncate3(text, 120),
|
|
2515
3699
|
detail: text,
|
|
3700
|
+
description: text,
|
|
2516
3701
|
severity: guessSeverity(text)
|
|
2517
3702
|
}));
|
|
2518
3703
|
return {
|
|
@@ -2523,7 +3708,8 @@ function extractReportFromText(raw, meta) {
|
|
|
2523
3708
|
summary,
|
|
2524
3709
|
findings,
|
|
2525
3710
|
recommendations: recommendationTexts.map((t) => truncate3(t, 200)),
|
|
2526
|
-
|
|
3711
|
+
...evidenceTexts.length > 0 ? { evidence: evidenceTexts.map((excerpt) => ({ kind: "observation", excerpt })) } : {},
|
|
3712
|
+
metadata: evidenceTexts.length > 0 ? { evidenceCount: evidenceTexts.length } : void 0,
|
|
2527
3713
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2528
3714
|
};
|
|
2529
3715
|
}
|
|
@@ -2539,27 +3725,46 @@ var ReportService = class {
|
|
|
2539
3725
|
}
|
|
2540
3726
|
store;
|
|
2541
3727
|
log;
|
|
3728
|
+
/** Rejected reports from the last collection (§13 — malformed never enters aggregation). */
|
|
3729
|
+
lastDiagnostics = { collected: 0, accepted: 0, rejected: [] };
|
|
2542
3730
|
get storeRef() {
|
|
2543
3731
|
return this.store;
|
|
2544
3732
|
}
|
|
2545
|
-
/** 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). */
|
|
2546
3735
|
async collectFromTasks(tasks) {
|
|
2547
|
-
const
|
|
3736
|
+
const candidates = [];
|
|
2548
3737
|
for (const task of tasks) {
|
|
2549
3738
|
const structured = task.result?.reports ?? [];
|
|
2550
3739
|
if (structured.length > 0) {
|
|
2551
|
-
|
|
3740
|
+
candidates.push(...structured);
|
|
2552
3741
|
continue;
|
|
2553
3742
|
}
|
|
2554
3743
|
const text = task.result?.rawOutput ?? task.result?.summary ?? "";
|
|
2555
3744
|
if (text.trim().length === 0) continue;
|
|
2556
|
-
|
|
3745
|
+
candidates.push(
|
|
2557
3746
|
extractReportFromText(text, {
|
|
2558
3747
|
taskId: task.id,
|
|
2559
3748
|
agent: task.agent ?? "unknown"
|
|
2560
3749
|
})
|
|
2561
3750
|
);
|
|
2562
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 };
|
|
2563
3768
|
return reports;
|
|
2564
3769
|
}
|
|
2565
3770
|
async saveReports(reports) {
|
|
@@ -2582,84 +3787,39 @@ var ReportService = class {
|
|
|
2582
3787
|
};
|
|
2583
3788
|
|
|
2584
3789
|
// src/reports/store.ts
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
filePath;
|
|
2589
|
-
queue = Promise.resolve();
|
|
2590
|
-
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
2591
|
-
this.filePath = `${directory}/reports.json`;
|
|
3790
|
+
var FileReportStore = class extends JsonFileStore {
|
|
3791
|
+
constructor(directory) {
|
|
3792
|
+
super({ ...directory !== void 0 ? { directory } : {}, fileName: "reports.json" });
|
|
2592
3793
|
}
|
|
2593
3794
|
async saveReports(reports) {
|
|
2594
|
-
|
|
2595
|
-
const entries = await this.load();
|
|
3795
|
+
await this.mutate((entries) => {
|
|
2596
3796
|
for (const report of reports) {
|
|
2597
|
-
const index = entries.findIndex(
|
|
2598
|
-
(e) => e.kind === "report" && e.report.id === report.id
|
|
2599
|
-
);
|
|
3797
|
+
const index = entries.findIndex((e) => e.kind === "report" && e.report.id === report.id);
|
|
2600
3798
|
const entry = { kind: "report", report };
|
|
2601
3799
|
if (index >= 0) entries[index] = entry;
|
|
2602
3800
|
else entries.push(entry);
|
|
2603
3801
|
}
|
|
2604
|
-
|
|
3802
|
+
return [entries, void 0];
|
|
2605
3803
|
});
|
|
2606
3804
|
}
|
|
2607
3805
|
async listReports(filter = {}) {
|
|
2608
|
-
return this.
|
|
2609
|
-
const entries = await this.load();
|
|
2610
|
-
return entries.filter((e) => e.kind === "report").map((e) => e.report).filter((report) => filter.taskId ? report.taskId === filter.taskId : true).filter((report) => filter.agent ? report.agent === filter.agent : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2611
|
-
});
|
|
3806
|
+
return (await this.readAll()).filter((e) => e.kind === "report").map((e) => e.report).filter((report) => filter.taskId ? report.taskId === filter.taskId : true).filter((report) => filter.agent ? report.agent === filter.agent : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2612
3807
|
}
|
|
2613
3808
|
async saveCombined(combined) {
|
|
2614
|
-
|
|
2615
|
-
const
|
|
2616
|
-
const index = entries.findIndex(
|
|
2617
|
-
(e) => e.kind === "combined" && e.combined.id === combined.id
|
|
2618
|
-
);
|
|
3809
|
+
await this.mutate((entries) => {
|
|
3810
|
+
const index = entries.findIndex((e) => e.kind === "combined" && e.combined.id === combined.id);
|
|
2619
3811
|
const entry = { kind: "combined", combined };
|
|
2620
3812
|
if (index >= 0) entries[index] = entry;
|
|
2621
3813
|
else entries.push(entry);
|
|
2622
|
-
|
|
3814
|
+
return [entries, void 0];
|
|
2623
3815
|
});
|
|
2624
3816
|
}
|
|
2625
3817
|
async getCombined(id) {
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
const found = entries.find((e) => e.kind === "combined" && e.combined.id === id);
|
|
2629
|
-
return found?.kind === "combined" ? found.combined : void 0;
|
|
2630
|
-
});
|
|
3818
|
+
const found = (await this.readAll()).find((e) => e.kind === "combined" && e.combined.id === id);
|
|
3819
|
+
return found?.kind === "combined" ? found.combined : void 0;
|
|
2631
3820
|
}
|
|
2632
3821
|
async listCombined(filter = {}) {
|
|
2633
|
-
return this.
|
|
2634
|
-
const entries = await this.load();
|
|
2635
|
-
return entries.filter((e) => e.kind === "combined").map((e) => e.combined).filter(
|
|
2636
|
-
(combined) => filter.pipelineRunId ? combined.pipelineRunId === filter.pipelineRunId : true
|
|
2637
|
-
).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2638
|
-
});
|
|
2639
|
-
}
|
|
2640
|
-
synchronized(operation) {
|
|
2641
|
-
const result = this.queue.then(operation, operation);
|
|
2642
|
-
this.queue = result.then(
|
|
2643
|
-
() => void 0,
|
|
2644
|
-
() => void 0
|
|
2645
|
-
);
|
|
2646
|
-
return result;
|
|
2647
|
-
}
|
|
2648
|
-
async load() {
|
|
2649
|
-
try {
|
|
2650
|
-
const raw = await readFile5(this.filePath, "utf8");
|
|
2651
|
-
const parsed = JSON.parse(raw);
|
|
2652
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
2653
|
-
} catch {
|
|
2654
|
-
return [];
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
async write(entries) {
|
|
2658
|
-
const tmp = `${this.filePath}.tmp`;
|
|
2659
|
-
await mkdir6(dirname6(this.filePath), { recursive: true });
|
|
2660
|
-
await writeFile5(tmp, `${JSON.stringify(entries, null, 2)}
|
|
2661
|
-
`, "utf8");
|
|
2662
|
-
await rename5(tmp, this.filePath);
|
|
3822
|
+
return (await this.readAll()).filter((e) => e.kind === "combined").map((e) => e.combined).filter((combined) => filter.pipelineRunId ? combined.pipelineRunId === filter.pipelineRunId : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2663
3823
|
}
|
|
2664
3824
|
};
|
|
2665
3825
|
|
|
@@ -2672,7 +3832,11 @@ function buildPipelineEngine(config, log = logger) {
|
|
|
2672
3832
|
agents,
|
|
2673
3833
|
roles,
|
|
2674
3834
|
store: new FileTaskStore(),
|
|
2675
|
-
...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
|
+
} : {},
|
|
2676
3840
|
audit: new FileAuditLog(),
|
|
2677
3841
|
log
|
|
2678
3842
|
});
|
|
@@ -2691,36 +3855,69 @@ function buildPipelineEngine(config, log = logger) {
|
|
|
2691
3855
|
});
|
|
2692
3856
|
}
|
|
2693
3857
|
|
|
3858
|
+
// src/persistence/index.ts
|
|
3859
|
+
var ProjectStores = class {
|
|
3860
|
+
tasks;
|
|
3861
|
+
pipelineRuns;
|
|
3862
|
+
reports;
|
|
3863
|
+
plans;
|
|
3864
|
+
memory;
|
|
3865
|
+
audit;
|
|
3866
|
+
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
3867
|
+
this.tasks = new FileTaskStore(directory);
|
|
3868
|
+
this.pipelineRuns = new FilePipelineRunStore(directory);
|
|
3869
|
+
this.reports = new FileReportStore(directory);
|
|
3870
|
+
this.plans = new FilePlanStore(directory);
|
|
3871
|
+
this.memory = new LocalMemoryProvider(directory);
|
|
3872
|
+
this.audit = new FileAuditLog(directory);
|
|
3873
|
+
}
|
|
3874
|
+
};
|
|
3875
|
+
|
|
2694
3876
|
// src/version.ts
|
|
2695
|
-
var OMNINODE_VERSION = "
|
|
3877
|
+
var OMNINODE_VERSION = "2.0.0-alpha.10";
|
|
2696
3878
|
export {
|
|
3879
|
+
AgentAdapterRegistry,
|
|
2697
3880
|
AgentError,
|
|
2698
3881
|
AgentRegistry,
|
|
2699
3882
|
ConfigError,
|
|
2700
3883
|
ConsoleLogSink,
|
|
3884
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
2701
3885
|
FileAuditLog,
|
|
2702
3886
|
FilePipelineRunStore,
|
|
3887
|
+
FilePlanStore,
|
|
2703
3888
|
FileReportStore,
|
|
2704
3889
|
FileTaskStore,
|
|
3890
|
+
HeuristicPlanner,
|
|
3891
|
+
JsonFileStore,
|
|
2705
3892
|
LocalMemoryProvider,
|
|
2706
3893
|
Logger,
|
|
3894
|
+
MAX_CONTEXT_CHARS,
|
|
3895
|
+
MAX_MESSAGE_BYTES,
|
|
2707
3896
|
MemoryError,
|
|
2708
3897
|
MemoryService,
|
|
3898
|
+
ModelPlanner,
|
|
2709
3899
|
ModelRegistry,
|
|
2710
3900
|
OMNINODE_VERSION,
|
|
2711
3901
|
OPENAI_COMPATIBLE_TYPES,
|
|
2712
3902
|
OmniHilbrasProvider,
|
|
2713
3903
|
OmniNodeError,
|
|
2714
3904
|
OpenAICompatibleProvider,
|
|
3905
|
+
PROTOCOL_V2,
|
|
2715
3906
|
PipelineEngine,
|
|
2716
3907
|
PipelineError,
|
|
2717
3908
|
PlannerError,
|
|
2718
3909
|
ProcessAgent,
|
|
3910
|
+
ProcessAgentAdapter,
|
|
3911
|
+
ProjectStores,
|
|
3912
|
+
ProtocolDecoder,
|
|
2719
3913
|
ProtocolError,
|
|
3914
|
+
ProtocolSession,
|
|
2720
3915
|
ProviderError,
|
|
2721
3916
|
RememberaMemoryProvider,
|
|
2722
3917
|
ReportService,
|
|
2723
3918
|
RoleRegistry,
|
|
3919
|
+
SUPPORTED_PROTOCOL_VERSIONS,
|
|
3920
|
+
TERMINAL_TASK_STATUSES,
|
|
2724
3921
|
TaskEngine,
|
|
2725
3922
|
TaskError,
|
|
2726
3923
|
agentConfigSchema,
|
|
@@ -2728,7 +3925,12 @@ export {
|
|
|
2728
3925
|
appConfigSchema,
|
|
2729
3926
|
authHeaders,
|
|
2730
3927
|
buildAgentRegistry,
|
|
3928
|
+
buildChildEnv,
|
|
3929
|
+
buildHello,
|
|
3930
|
+
buildHelloAck,
|
|
2731
3931
|
buildPipelineEngine,
|
|
3932
|
+
buildPlanner,
|
|
3933
|
+
buildPlannerContext,
|
|
2732
3934
|
buildRoleRegistry,
|
|
2733
3935
|
composeTaskText,
|
|
2734
3936
|
createAgent,
|
|
@@ -2736,23 +3938,52 @@ export {
|
|
|
2736
3938
|
createMemoryProvider,
|
|
2737
3939
|
createProvider,
|
|
2738
3940
|
createTaskEngine,
|
|
3941
|
+
decodeMessage,
|
|
3942
|
+
decodeStream,
|
|
3943
|
+
defaultMetadata,
|
|
2739
3944
|
defaultProjectConfigYaml,
|
|
3945
|
+
defaultSearch,
|
|
3946
|
+
describeExit,
|
|
3947
|
+
detectConflicts,
|
|
3948
|
+
encodeMessage,
|
|
3949
|
+
envelope,
|
|
2740
3950
|
expandEnvRefs,
|
|
3951
|
+
extractMessage,
|
|
2741
3952
|
extractReportFromText,
|
|
2742
3953
|
findConfigFile,
|
|
3954
|
+
isNetworkError,
|
|
2743
3955
|
isOmniNodeError,
|
|
3956
|
+
isRetryable,
|
|
3957
|
+
isSupportedProtocol,
|
|
3958
|
+
isTimeoutError,
|
|
2744
3959
|
keywords,
|
|
3960
|
+
killTree,
|
|
3961
|
+
kindFromStatus,
|
|
2745
3962
|
loadConfig,
|
|
2746
3963
|
logger,
|
|
3964
|
+
newMessageId,
|
|
3965
|
+
normalizeEvidence,
|
|
3966
|
+
normalizeFinding,
|
|
3967
|
+
parseHello,
|
|
3968
|
+
parsePlanJson,
|
|
3969
|
+
parseRetryAfter,
|
|
2747
3970
|
pipelineConfigSchema,
|
|
2748
3971
|
pipelineStepConfigSchema,
|
|
2749
3972
|
plannerConfigSchema,
|
|
2750
3973
|
projectConfigSchema,
|
|
2751
3974
|
providerConfigSchema,
|
|
3975
|
+
providerHttpError,
|
|
3976
|
+
providerTransportError,
|
|
2752
3977
|
registryKey,
|
|
2753
3978
|
requestJson,
|
|
3979
|
+
requireCompletion,
|
|
2754
3980
|
resolveApiKey,
|
|
3981
|
+
resolveEnvPolicy,
|
|
2755
3982
|
roleConfigSchema,
|
|
2756
|
-
|
|
3983
|
+
sanitizeChunk,
|
|
3984
|
+
similar,
|
|
3985
|
+
validateEnvelope,
|
|
3986
|
+
validateReport,
|
|
3987
|
+
validateWorkingDirectory
|
|
2757
3988
|
};
|
|
2758
3989
|
//# sourceMappingURL=index.js.map
|