@hilbras/omninode 2.0.0-alpha.4 → 2.0.0-alpha.6
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 +19 -4
- package/dist/cli/index.js +359 -82
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +143 -7
- package/dist/index.js +339 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -394,56 +394,184 @@ function authHeaders(config, env = process.env) {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
// src/providers/http.ts
|
|
397
|
+
var HttpResponseError = class extends Error {
|
|
398
|
+
constructor(message, status, cause) {
|
|
399
|
+
super(message, { cause });
|
|
400
|
+
this.status = status;
|
|
401
|
+
this.name = "HttpResponseError";
|
|
402
|
+
}
|
|
403
|
+
status;
|
|
404
|
+
};
|
|
397
405
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
398
406
|
async function requestJson(url, options = {}) {
|
|
399
407
|
const { method = "GET", headers = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = options;
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
411
|
-
});
|
|
412
|
-
} catch (error) {
|
|
413
|
-
const reason = error instanceof Error && error.name === "TimeoutError" ? `timed out after ${timeoutMs}ms` : error instanceof Error ? error.message : String(error);
|
|
414
|
-
throw new ProviderError("PROVIDER_UNAVAILABLE", `Request to ${url} failed: ${reason}.`, {
|
|
415
|
-
cause: error
|
|
416
|
-
});
|
|
417
|
-
}
|
|
408
|
+
const response = await fetch(url, {
|
|
409
|
+
method,
|
|
410
|
+
headers: {
|
|
411
|
+
accept: "application/json",
|
|
412
|
+
...body !== void 0 ? { "content-type": "application/json" } : {},
|
|
413
|
+
...headers
|
|
414
|
+
},
|
|
415
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
416
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
417
|
+
});
|
|
418
418
|
let parsed;
|
|
419
419
|
try {
|
|
420
420
|
parsed = await response.json();
|
|
421
421
|
} catch (error) {
|
|
422
|
-
throw new
|
|
423
|
-
cause: error,
|
|
424
|
-
details: { status: response.status }
|
|
425
|
-
});
|
|
422
|
+
throw new HttpResponseError(`Response from ${url} was not valid JSON.`, response.status, error);
|
|
426
423
|
}
|
|
427
424
|
return { status: response.status, body: parsed };
|
|
428
425
|
}
|
|
429
426
|
|
|
427
|
+
// src/providers/errors.ts
|
|
428
|
+
function kindToCode(kind) {
|
|
429
|
+
switch (kind) {
|
|
430
|
+
case "AUTHENTICATION_ERROR":
|
|
431
|
+
return "PROVIDER_AUTH_FAILED";
|
|
432
|
+
case "MODEL_NOT_FOUND":
|
|
433
|
+
return "MODEL_NOT_FOUND";
|
|
434
|
+
default:
|
|
435
|
+
return "PROVIDER_UNAVAILABLE";
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function isRetryable(kind) {
|
|
439
|
+
return kind === "RATE_LIMIT_ERROR" || kind === "SERVER_ERROR" || kind === "NETWORK_ERROR";
|
|
440
|
+
}
|
|
441
|
+
function kindFromStatus(status) {
|
|
442
|
+
if (status === 401 || status === 403) return "AUTHENTICATION_ERROR";
|
|
443
|
+
if (status === 429) return "RATE_LIMIT_ERROR";
|
|
444
|
+
if (status === 404) return "MODEL_NOT_FOUND";
|
|
445
|
+
if (status === 408 || status === 504) return "TIMEOUT";
|
|
446
|
+
if (status >= 500) return "SERVER_ERROR";
|
|
447
|
+
if (status >= 400) return "INVALID_REQUEST";
|
|
448
|
+
return "UNKNOWN_ERROR";
|
|
449
|
+
}
|
|
450
|
+
function extractMessage(body) {
|
|
451
|
+
if (body === null || typeof body !== "object") return void 0;
|
|
452
|
+
const root = body;
|
|
453
|
+
const error = root.error ?? root;
|
|
454
|
+
const message = error.message ?? error.detail ?? root.message;
|
|
455
|
+
return typeof message === "string" ? message : void 0;
|
|
456
|
+
}
|
|
457
|
+
function parseRetryAfter(value, now = Date.now()) {
|
|
458
|
+
if (!value) return void 0;
|
|
459
|
+
const seconds = Number(value);
|
|
460
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
461
|
+
const date = Date.parse(value);
|
|
462
|
+
return Number.isNaN(date) ? void 0 : Math.max(0, date - now);
|
|
463
|
+
}
|
|
464
|
+
function isTimeoutError(error) {
|
|
465
|
+
return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError") || typeof error?.code === "string" && error.code === "ABORT_ERR";
|
|
466
|
+
}
|
|
467
|
+
function isNetworkError(error) {
|
|
468
|
+
return error instanceof TypeError || typeof error?.cause === "object" && error instanceof Error && !error.code;
|
|
469
|
+
}
|
|
470
|
+
function fallbackMessage(kind, context, status) {
|
|
471
|
+
const { provider, operation } = context;
|
|
472
|
+
switch (kind) {
|
|
473
|
+
case "AUTHENTICATION_ERROR":
|
|
474
|
+
return `Authentication with provider "${provider}" failed (HTTP ${status}). Check that the referenced API key is set and valid.`;
|
|
475
|
+
case "RATE_LIMIT_ERROR":
|
|
476
|
+
return `Provider "${provider}" rate-limited ${operation} (HTTP ${status}). Retry later.`;
|
|
477
|
+
case "MODEL_NOT_FOUND":
|
|
478
|
+
return `Provider "${provider}" does not expose the requested model (HTTP ${status}).`;
|
|
479
|
+
case "TIMEOUT":
|
|
480
|
+
return `Provider "${provider}" timed out during ${operation} (HTTP ${status}).`;
|
|
481
|
+
case "SERVER_ERROR":
|
|
482
|
+
return `Provider "${provider}" returned a server error during ${operation} (HTTP ${status}).`;
|
|
483
|
+
case "INVALID_REQUEST":
|
|
484
|
+
return `Provider "${provider}" rejected ${operation} as invalid (HTTP ${status}).`;
|
|
485
|
+
default:
|
|
486
|
+
return `Provider "${provider}" failed during ${operation} (HTTP ${status}).`;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function providerHttpError(context) {
|
|
490
|
+
const status = context.status ?? 0;
|
|
491
|
+
const kind = kindFromStatus(status);
|
|
492
|
+
const detail = extractMessage(context.body);
|
|
493
|
+
const retryAfterMs = context.retryAfterMs ?? parseRetryAfter(void 0);
|
|
494
|
+
return new ProviderError(
|
|
495
|
+
context.code ?? kindToCode(kind),
|
|
496
|
+
detail ?? fallbackMessage(kind, context, status),
|
|
497
|
+
{
|
|
498
|
+
cause: context.cause,
|
|
499
|
+
details: {
|
|
500
|
+
kind,
|
|
501
|
+
provider: context.provider,
|
|
502
|
+
operation: context.operation,
|
|
503
|
+
status,
|
|
504
|
+
retryable: isRetryable(kind),
|
|
505
|
+
...retryAfterMs !== void 0 ? { retryAfterMs } : {},
|
|
506
|
+
...detail ? { providerMessage: detail } : {}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
function providerTransportError(context) {
|
|
512
|
+
const cause = context.cause;
|
|
513
|
+
const kind = isTimeoutError(cause) ? "TIMEOUT" : cause instanceof SyntaxError || cause?.name === "HttpResponseError" ? "UNKNOWN_ERROR" : "NETWORK_ERROR";
|
|
514
|
+
const message = context.cause instanceof Error ? context.cause.message : String(context.cause);
|
|
515
|
+
return new ProviderError("PROVIDER_UNAVAILABLE", `Provider request failed: ${message}`, {
|
|
516
|
+
cause: context.cause,
|
|
517
|
+
details: {
|
|
518
|
+
kind,
|
|
519
|
+
provider: context.provider,
|
|
520
|
+
operation: context.operation,
|
|
521
|
+
retryable: isRetryable(kind)
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
430
526
|
// src/providers/openai-compatible/index.ts
|
|
431
527
|
var OpenAICompatibleProvider = class {
|
|
432
528
|
config;
|
|
529
|
+
providerId;
|
|
530
|
+
authentication;
|
|
433
531
|
log;
|
|
434
|
-
|
|
532
|
+
/** Discovered models, so getModel() answers without another round-trip. */
|
|
533
|
+
discovered;
|
|
534
|
+
env;
|
|
535
|
+
constructor(config, log = logger, env = process.env) {
|
|
435
536
|
this.config = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
|
|
537
|
+
this.env = env;
|
|
538
|
+
this.providerId = config.providerId ?? config.name;
|
|
539
|
+
this.authentication = {
|
|
540
|
+
method: config.apiKeyEnvVar ? "bearer" : "none",
|
|
541
|
+
...config.apiKeyEnvVar ? { envVar: config.apiKeyEnvVar } : {},
|
|
542
|
+
configured: config.apiKeyEnvVar ? Boolean(env[config.apiKeyEnvVar]) : true
|
|
543
|
+
};
|
|
436
544
|
this.log = log.child({ provider: config.name });
|
|
437
545
|
}
|
|
546
|
+
/** Authenticate + discover (roadmap §10, Model Discovery). */
|
|
547
|
+
async connect() {
|
|
548
|
+
authHeaders(this.config, this.env);
|
|
549
|
+
const models = await this.listModels();
|
|
550
|
+
this.discovered = models;
|
|
551
|
+
return models;
|
|
552
|
+
}
|
|
553
|
+
async getModel(modelId) {
|
|
554
|
+
const models = this.discovered ?? await this.connect();
|
|
555
|
+
return models.find((m) => m.id === modelId);
|
|
556
|
+
}
|
|
438
557
|
async listModels() {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
558
|
+
let response;
|
|
559
|
+
try {
|
|
560
|
+
response = await requestJson(`${this.config.baseUrl}/models`, {
|
|
561
|
+
headers: authHeaders(this.config, this.env)
|
|
562
|
+
});
|
|
563
|
+
} catch (error) {
|
|
564
|
+
if (error instanceof ProviderError) throw error;
|
|
565
|
+
throw providerTransportError({ provider: this.config.name, operation: "listModels", cause: error });
|
|
444
566
|
}
|
|
567
|
+
const { status, body } = response;
|
|
445
568
|
if (status !== 200) {
|
|
446
|
-
throw
|
|
569
|
+
throw providerHttpError({
|
|
570
|
+
provider: this.config.name,
|
|
571
|
+
operation: "listModels",
|
|
572
|
+
status,
|
|
573
|
+
body
|
|
574
|
+
});
|
|
447
575
|
}
|
|
448
576
|
const entries = extractModelEntries(body, this.config);
|
|
449
577
|
const models = [];
|
|
@@ -453,22 +581,40 @@ var OpenAICompatibleProvider = class {
|
|
|
453
581
|
skipped += 1;
|
|
454
582
|
continue;
|
|
455
583
|
}
|
|
456
|
-
models.push(
|
|
457
|
-
provider: this.config.name,
|
|
458
|
-
id: entry.id,
|
|
459
|
-
displayName: entry.id,
|
|
460
|
-
status: "available",
|
|
461
|
-
metadata: {
|
|
462
|
-
...typeof entry.owned_by === "string" ? { ownedBy: entry.owned_by } : {},
|
|
463
|
-
...typeof entry.created === "number" ? { createdAt: entry.created } : {}
|
|
464
|
-
}
|
|
465
|
-
});
|
|
584
|
+
models.push(this.toModelInfo(entry));
|
|
466
585
|
}
|
|
467
586
|
if (skipped > 0) {
|
|
468
587
|
this.log.warn(`Skipped ${skipped} model entr(y/ies) without a valid id.`);
|
|
469
588
|
}
|
|
470
589
|
return models;
|
|
471
590
|
}
|
|
591
|
+
/** Maps a raw model entry onto the normalized metadata shape (§10). */
|
|
592
|
+
toModelInfo(entry) {
|
|
593
|
+
const parameters = Array.isArray(entry.supported_parameters) ? entry.supported_parameters.filter((p) => typeof p === "string") : void 0;
|
|
594
|
+
const contextWindow = typeof entry.context_length === "number" ? entry.context_length : typeof entry.context_window === "number" ? entry.context_window : void 0;
|
|
595
|
+
const inputTypes = normalizeModalities(entry.input_modalities);
|
|
596
|
+
const outputTypes = normalizeModalities(entry.output_modalities);
|
|
597
|
+
return {
|
|
598
|
+
provider: this.config.name,
|
|
599
|
+
id: entry.id,
|
|
600
|
+
displayName: entry.id,
|
|
601
|
+
...typeof entry.name === "string" ? { name: entry.name } : {},
|
|
602
|
+
status: "available",
|
|
603
|
+
...contextWindow !== void 0 ? { contextWindow } : {},
|
|
604
|
+
...inputTypes !== void 0 ? { inputTypes } : {},
|
|
605
|
+
...outputTypes !== void 0 ? { outputTypes } : {},
|
|
606
|
+
...parameters !== void 0 ? {
|
|
607
|
+
supportsTools: parameters.includes("tools") || parameters.includes("tool_choice"),
|
|
608
|
+
supportsStructuredOutput: parameters.includes("response_format"),
|
|
609
|
+
supportsStreaming: parameters.includes("stream")
|
|
610
|
+
} : {},
|
|
611
|
+
metadata: {
|
|
612
|
+
...typeof entry.owned_by === "string" ? { ownedBy: entry.owned_by } : {},
|
|
613
|
+
...typeof entry.created === "number" ? { createdAt: entry.created } : {},
|
|
614
|
+
...parameters !== void 0 ? { supportedParameters: parameters } : {}
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
472
618
|
async healthCheck() {
|
|
473
619
|
const lastCheckedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
474
620
|
try {
|
|
@@ -498,27 +644,34 @@ var OpenAICompatibleProvider = class {
|
|
|
498
644
|
}
|
|
499
645
|
}
|
|
500
646
|
async chat(request) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
{ details: { status } }
|
|
518
|
-
);
|
|
647
|
+
let response;
|
|
648
|
+
try {
|
|
649
|
+
response = await requestJson(`${this.config.baseUrl}/chat/completions`, {
|
|
650
|
+
method: "POST",
|
|
651
|
+
headers: authHeaders(this.config, this.env),
|
|
652
|
+
body: {
|
|
653
|
+
model: request.model,
|
|
654
|
+
messages: request.messages,
|
|
655
|
+
...request.temperature !== void 0 ? { temperature: request.temperature } : {},
|
|
656
|
+
...request.maxTokens !== void 0 ? { max_tokens: request.maxTokens } : {},
|
|
657
|
+
...request.stop !== void 0 ? { stop: request.stop } : {}
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
} catch (error) {
|
|
661
|
+
if (error instanceof ProviderError) throw error;
|
|
662
|
+
throw providerTransportError({ provider: this.config.name, operation: "chat", cause: error });
|
|
519
663
|
}
|
|
664
|
+
const { status, body } = response;
|
|
520
665
|
if (status !== 200) {
|
|
521
|
-
throw
|
|
666
|
+
throw providerHttpError({
|
|
667
|
+
provider: this.config.name,
|
|
668
|
+
operation: "chat",
|
|
669
|
+
status,
|
|
670
|
+
body,
|
|
671
|
+
...status === 404 ? {
|
|
672
|
+
code: "MODEL_NOT_FOUND"
|
|
673
|
+
} : {}
|
|
674
|
+
});
|
|
522
675
|
}
|
|
523
676
|
const payload = body;
|
|
524
677
|
const choices = Array.isArray(payload.choices) ? payload.choices : [];
|
|
@@ -540,6 +693,13 @@ var OpenAICompatibleProvider = class {
|
|
|
540
693
|
};
|
|
541
694
|
}
|
|
542
695
|
};
|
|
696
|
+
function normalizeModalities(value) {
|
|
697
|
+
const list = Array.isArray(value) ? value : typeof value === "string" ? [value] : void 0;
|
|
698
|
+
if (!list) return void 0;
|
|
699
|
+
const allowed = /* @__PURE__ */ new Set(["text", "image", "audio", "video", "embedding"]);
|
|
700
|
+
const modalities = list.filter((m) => typeof m === "string" && allowed.has(m));
|
|
701
|
+
return modalities.length > 0 ? modalities : void 0;
|
|
702
|
+
}
|
|
543
703
|
function normalizeBaseUrl(baseUrl) {
|
|
544
704
|
return baseUrl.replace(/\/+$/, "");
|
|
545
705
|
}
|
|
@@ -554,14 +714,6 @@ function extractModelEntries(body, config) {
|
|
|
554
714
|
{ details: { endpoint: "models" } }
|
|
555
715
|
);
|
|
556
716
|
}
|
|
557
|
-
function authFailed(config, status) {
|
|
558
|
-
const envHint = config.apiKeyEnvVar ? ` Check that $${config.apiKeyEnvVar} is set and valid.` : " The provider requires authentication; configure api_key_env_var for it.";
|
|
559
|
-
return new ProviderError(
|
|
560
|
-
"PROVIDER_AUTH_FAILED",
|
|
561
|
-
`Authentication with provider "${config.name}" failed (HTTP ${status}).${envHint}`,
|
|
562
|
-
{ details: { status } }
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
717
|
function unavailable(config, message, status) {
|
|
566
718
|
return new ProviderError("PROVIDER_UNAVAILABLE", `Provider "${config.name}": ${message}`, {
|
|
567
719
|
details: { status }
|
|
@@ -578,7 +730,7 @@ var OmniHilbrasProvider = class extends OpenAICompatibleProvider {
|
|
|
578
730
|
* Run the full connect flow. Throws on authentication or transport failure
|
|
579
731
|
* (use healthCheck() for a non-throwing probe).
|
|
580
732
|
*/
|
|
581
|
-
async
|
|
733
|
+
async connectAndRegister(registry) {
|
|
582
734
|
const apiKey = resolveApiKey(this.config);
|
|
583
735
|
const discovered = await this.listModels();
|
|
584
736
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1430,7 +1582,7 @@ function describeExit(code, signal) {
|
|
|
1430
1582
|
return `Process exited with code ${code}.`;
|
|
1431
1583
|
}
|
|
1432
1584
|
function sanitizeChunk(chunk) {
|
|
1433
|
-
return chunk.toString("utf8").
|
|
1585
|
+
return [...chunk.toString("utf8")].filter((char) => char.charCodeAt(0) !== 0).join("");
|
|
1434
1586
|
}
|
|
1435
1587
|
function normalizeReport(payload, taskId, agentName, index) {
|
|
1436
1588
|
if (payload === null || typeof payload !== "object") return void 0;
|
|
@@ -1777,6 +1929,11 @@ var TaskEngine = class {
|
|
|
1777
1929
|
if (this.options.memory) await this.options.memory.recordTaskOutcome(current);
|
|
1778
1930
|
return current;
|
|
1779
1931
|
}
|
|
1932
|
+
const currentState = await this.options.store.get(current.id);
|
|
1933
|
+
if (currentState?.status === "cancelled") {
|
|
1934
|
+
current = currentState;
|
|
1935
|
+
break;
|
|
1936
|
+
}
|
|
1780
1937
|
const result = {
|
|
1781
1938
|
...output.summary !== void 0 ? { summary: output.summary } : {},
|
|
1782
1939
|
...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
|
|
@@ -2439,9 +2596,23 @@ var PipelineEngine = class {
|
|
|
2439
2596
|
}
|
|
2440
2597
|
options;
|
|
2441
2598
|
log;
|
|
2599
|
+
/** Runs executing in THIS process — cancellation can terminate them directly. */
|
|
2600
|
+
activeRuns = /* @__PURE__ */ new Map();
|
|
2442
2601
|
get store() {
|
|
2443
2602
|
return this.options.store;
|
|
2444
2603
|
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Saves the run while preserving fields that may be set by another process
|
|
2606
|
+
* (e.g. a cancellation request) — an in-memory snapshot must never clobber
|
|
2607
|
+
* externally requested cancellation (§9.2).
|
|
2608
|
+
*/
|
|
2609
|
+
async persist(run) {
|
|
2610
|
+
const current = await this.options.store.get(run.id);
|
|
2611
|
+
await this.options.store.save({
|
|
2612
|
+
...run,
|
|
2613
|
+
...current?.cancellationRequested === true ? { cancellationRequested: true, cancellationRequestedAt: current.cancellationRequestedAt } : {}
|
|
2614
|
+
});
|
|
2615
|
+
}
|
|
2445
2616
|
/** Structural + semantic validation (§27 Phase 5 — pipeline parser). */
|
|
2446
2617
|
validate(def) {
|
|
2447
2618
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -2541,18 +2712,44 @@ var PipelineEngine = class {
|
|
|
2541
2712
|
);
|
|
2542
2713
|
}
|
|
2543
2714
|
}
|
|
2715
|
+
if (step.model) {
|
|
2716
|
+
const separator = step.model.indexOf(":");
|
|
2717
|
+
if (separator <= 0 || separator === step.model.length - 1) {
|
|
2718
|
+
throw new PipelineError(
|
|
2719
|
+
"PIPELINE_INVALID",
|
|
2720
|
+
`Step "${step.id}" has an invalid model reference "${step.model}" \u2014 use "provider:model-id".`
|
|
2721
|
+
);
|
|
2722
|
+
}
|
|
2723
|
+
const providerName = step.model.slice(0, separator);
|
|
2724
|
+
if (!this.options.providers.some((p) => p.name === providerName)) {
|
|
2725
|
+
throw new PipelineError(
|
|
2726
|
+
"PIPELINE_INVALID",
|
|
2727
|
+
`Step "${step.id}" uses model "${step.model}" but provider "${providerName}" is not configured.`
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2544
2731
|
}
|
|
2545
2732
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2546
2733
|
const run = {
|
|
2547
2734
|
id: `run-${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`,
|
|
2548
2735
|
pipelineId: def.id,
|
|
2736
|
+
attempt: 1,
|
|
2549
2737
|
objective,
|
|
2550
2738
|
status: "running",
|
|
2551
2739
|
startedAt: now,
|
|
2552
2740
|
stepRuns: []
|
|
2553
2741
|
};
|
|
2554
2742
|
await this.options.store.save(run);
|
|
2555
|
-
|
|
2743
|
+
const liveTaskIds = /* @__PURE__ */ new Set();
|
|
2744
|
+
this.activeRuns.set(run.id, {
|
|
2745
|
+
taskIds: liveTaskIds,
|
|
2746
|
+
cancelTasks: async () => {
|
|
2747
|
+
for (const taskId of liveTaskIds) {
|
|
2748
|
+
await this.options.tasks.cancel(taskId).catch(() => void 0);
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
});
|
|
2752
|
+
this.log.info(`Running pipeline ${def.id} as ${run.id} (attempt ${run.attempt}).`);
|
|
2556
2753
|
await this.options.audit?.record({
|
|
2557
2754
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2558
2755
|
action: "pipeline.run.started",
|
|
@@ -2564,7 +2761,22 @@ var PipelineEngine = class {
|
|
|
2564
2761
|
const pending = new Set(def.steps.map((s) => s.id));
|
|
2565
2762
|
const contextParts = [];
|
|
2566
2763
|
const allReports = [];
|
|
2764
|
+
let cancelled = false;
|
|
2567
2765
|
while (pending.size > 0) {
|
|
2766
|
+
const persisted = await this.options.store.get(run.id);
|
|
2767
|
+
if (persisted?.cancellationRequested === true) {
|
|
2768
|
+
cancelled = true;
|
|
2769
|
+
run.cancellationRequested = true;
|
|
2770
|
+
run.cancellationRequestedAt = persisted.cancellationRequestedAt;
|
|
2771
|
+
await this.activeRuns.get(run.id)?.cancelTasks();
|
|
2772
|
+
for (const step of def.steps.filter((s) => pending.has(s.id))) {
|
|
2773
|
+
const stepRun = { stepId: step.id, status: "cancelled" };
|
|
2774
|
+
run.stepRuns.push(stepRun);
|
|
2775
|
+
options.onStep?.(stepRun);
|
|
2776
|
+
}
|
|
2777
|
+
pending.clear();
|
|
2778
|
+
break;
|
|
2779
|
+
}
|
|
2568
2780
|
const ready = def.steps.filter(
|
|
2569
2781
|
(step) => pending.has(step.id) && (deps.get(step.id) ?? []).every((d) => outcomes.has(d))
|
|
2570
2782
|
);
|
|
@@ -2581,7 +2793,8 @@ var PipelineEngine = class {
|
|
|
2581
2793
|
...role ? { role } : {},
|
|
2582
2794
|
combinedContext: contextParts.join("\n\n"),
|
|
2583
2795
|
reports: [...allReports],
|
|
2584
|
-
pipelineRunId: run.id
|
|
2796
|
+
pipelineRunId: run.id,
|
|
2797
|
+
runId: run.id
|
|
2585
2798
|
};
|
|
2586
2799
|
return this.executeStep(step, ctx);
|
|
2587
2800
|
})
|
|
@@ -2598,6 +2811,8 @@ ${contribution}`);
|
|
|
2598
2811
|
}
|
|
2599
2812
|
const stepRun = {
|
|
2600
2813
|
stepId: step.id,
|
|
2814
|
+
attempt: outcome.attempt ?? 1,
|
|
2815
|
+
...outcome.executionIds && outcome.executionIds.length > 0 ? { executionIds: outcome.executionIds } : {},
|
|
2601
2816
|
status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : outcome.status === "partial" ? "partially_completed" : "completed",
|
|
2602
2817
|
...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
|
|
2603
2818
|
...outcome.planId !== void 0 ? { planId: outcome.planId } : {},
|
|
@@ -2607,12 +2822,12 @@ ${contribution}`);
|
|
|
2607
2822
|
options.onStep?.(stepRun);
|
|
2608
2823
|
});
|
|
2609
2824
|
run.reports = [...allReports];
|
|
2610
|
-
await this.
|
|
2825
|
+
await this.persist(run);
|
|
2611
2826
|
}
|
|
2612
2827
|
const allOutcomes = [...outcomes.values()];
|
|
2613
|
-
const failed = allOutcomes.some((o) => o.status === "failed");
|
|
2614
|
-
const partial = !failed && allOutcomes.some((o) => o.status === "partial");
|
|
2615
|
-
const finalStatus = failed ? "failed" : partial ? "partial" : "completed";
|
|
2828
|
+
const failed = !cancelled && allOutcomes.some((o) => o.status === "failed");
|
|
2829
|
+
const partial = !cancelled && !failed && allOutcomes.some((o) => o.status === "partial");
|
|
2830
|
+
const finalStatus = cancelled ? "cancelled" : failed ? "failed" : partial ? "partial" : "completed";
|
|
2616
2831
|
const planId = [...outcomes.values()].find((o) => o.planId !== void 0)?.planId;
|
|
2617
2832
|
let resultSummary;
|
|
2618
2833
|
for (let i = def.steps.length - 1; i >= 0; i -= 1) {
|
|
@@ -2640,16 +2855,19 @@ ${contribution}`);
|
|
|
2640
2855
|
run.reports = collected;
|
|
2641
2856
|
}
|
|
2642
2857
|
}
|
|
2858
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2859
|
+
this.activeRuns.delete(run.id);
|
|
2643
2860
|
const finished = {
|
|
2644
2861
|
...run,
|
|
2645
2862
|
status: finalStatus,
|
|
2646
|
-
finishedAt
|
|
2863
|
+
finishedAt,
|
|
2864
|
+
attempts: [...run.attempts ?? [], { attempt: run.attempt, startedAt: run.startedAt ?? finishedAt, finishedAt, status: finalStatus }],
|
|
2647
2865
|
...run.reports !== void 0 && run.reports.length > 0 ? { reports: run.reports } : {},
|
|
2648
2866
|
...combinedReportId !== void 0 ? { combinedReportId } : {},
|
|
2649
2867
|
...planId !== void 0 ? { planId } : {},
|
|
2650
2868
|
...resultSummary !== void 0 ? { resultSummary } : {}
|
|
2651
2869
|
};
|
|
2652
|
-
await this.
|
|
2870
|
+
await this.persist(finished);
|
|
2653
2871
|
this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
|
|
2654
2872
|
await this.options.audit?.record({
|
|
2655
2873
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2659,6 +2877,23 @@ ${contribution}`);
|
|
|
2659
2877
|
});
|
|
2660
2878
|
return finished;
|
|
2661
2879
|
}
|
|
2880
|
+
/** First-class cancellation (§9.2): requests cancellation and terminates
|
|
2881
|
+
* tasks that are running in this process immediately. */
|
|
2882
|
+
async cancel(runId) {
|
|
2883
|
+
const run = await this.options.store.get(runId);
|
|
2884
|
+
if (!run) {
|
|
2885
|
+
throw new PipelineError("PIPELINE_NOT_FOUND", `Pipeline run "${runId}" does not exist.`);
|
|
2886
|
+
}
|
|
2887
|
+
await this.options.store.save({
|
|
2888
|
+
...run,
|
|
2889
|
+
cancellationRequested: true,
|
|
2890
|
+
cancellationRequestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2891
|
+
});
|
|
2892
|
+
const active = this.activeRuns.get(runId);
|
|
2893
|
+
if (active) {
|
|
2894
|
+
await active.cancelTasks();
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2662
2897
|
async get(runId) {
|
|
2663
2898
|
return this.options.store.get(runId);
|
|
2664
2899
|
}
|
|
@@ -2682,9 +2917,16 @@ ${contribution}`);
|
|
|
2682
2917
|
this.log.warn(`Step "${step.id}" failed \u2014 retry ${attempt}/${retries}.`);
|
|
2683
2918
|
}
|
|
2684
2919
|
last = await this.executeStepOnce(step, ctx);
|
|
2685
|
-
if (last.status !== "failed") return last;
|
|
2920
|
+
if (last.status !== "failed") return { ...last, attempt: attempt + 1 };
|
|
2686
2921
|
}
|
|
2687
|
-
return last;
|
|
2922
|
+
return { ...last, attempt: retries + 1 };
|
|
2923
|
+
}
|
|
2924
|
+
/** Registers tasks created for a step as live, so cancellation can terminate them (§9.2). */
|
|
2925
|
+
trackLiveTasks(runId, taskIds) {
|
|
2926
|
+
if (!runId) return;
|
|
2927
|
+
const active = this.activeRuns.get(runId);
|
|
2928
|
+
if (!active) return;
|
|
2929
|
+
for (const id of taskIds) active.taskIds.add(id);
|
|
2688
2930
|
}
|
|
2689
2931
|
async executeStepOnce(step, ctx) {
|
|
2690
2932
|
switch (step.kind) {
|
|
@@ -2717,7 +2959,9 @@ ${contribution}`);
|
|
|
2717
2959
|
})
|
|
2718
2960
|
)
|
|
2719
2961
|
);
|
|
2962
|
+
this.trackLiveTasks(ctx.runId, created.map((t) => t.id));
|
|
2720
2963
|
const results = await Promise.all(created.map((task) => this.options.tasks.run(task.id)));
|
|
2964
|
+
const executionIds = results.map((r) => r.executions?.at(-1)?.executionId).filter((id) => id !== void 0);
|
|
2721
2965
|
const completedTasks = results.filter((r) => r.status === "completed");
|
|
2722
2966
|
const failedTasks = results.filter((r) => r.status !== "completed");
|
|
2723
2967
|
const lines = results.map((result, index) => {
|
|
@@ -2740,7 +2984,13 @@ ${contribution}`);
|
|
|
2740
2984
|
reports
|
|
2741
2985
|
};
|
|
2742
2986
|
}
|
|
2743
|
-
return {
|
|
2987
|
+
return {
|
|
2988
|
+
status: "completed",
|
|
2989
|
+
summary,
|
|
2990
|
+
taskIds: created.map((t) => t.id),
|
|
2991
|
+
...executionIds.length > 0 ? { executionIds } : {},
|
|
2992
|
+
reports
|
|
2993
|
+
};
|
|
2744
2994
|
}
|
|
2745
2995
|
async runSingle(step, ctx, agentName) {
|
|
2746
2996
|
const task = await this.options.tasks.create({
|
|
@@ -2749,6 +2999,7 @@ ${contribution}`);
|
|
|
2749
2999
|
...ctx.role ? { role: ctx.role.id } : {},
|
|
2750
3000
|
...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
|
|
2751
3001
|
});
|
|
3002
|
+
this.trackLiveTasks(ctx.runId, [task.id]);
|
|
2752
3003
|
const result = await this.options.tasks.run(task.id);
|
|
2753
3004
|
if (result.status !== "completed") {
|
|
2754
3005
|
return {
|
|
@@ -3226,7 +3477,7 @@ var ProjectStores = class {
|
|
|
3226
3477
|
};
|
|
3227
3478
|
|
|
3228
3479
|
// src/version.ts
|
|
3229
|
-
var OMNINODE_VERSION = "2.0.0-alpha.
|
|
3480
|
+
var OMNINODE_VERSION = "2.0.0-alpha.6";
|
|
3230
3481
|
export {
|
|
3231
3482
|
AgentAdapterRegistry,
|
|
3232
3483
|
AgentError,
|
|
@@ -3296,22 +3547,30 @@ export {
|
|
|
3296
3547
|
encodeMessage,
|
|
3297
3548
|
envelope,
|
|
3298
3549
|
expandEnvRefs,
|
|
3550
|
+
extractMessage,
|
|
3299
3551
|
extractReportFromText,
|
|
3300
3552
|
findConfigFile,
|
|
3553
|
+
isNetworkError,
|
|
3301
3554
|
isOmniNodeError,
|
|
3555
|
+
isRetryable,
|
|
3302
3556
|
isSupportedProtocol,
|
|
3557
|
+
isTimeoutError,
|
|
3303
3558
|
keywords,
|
|
3304
3559
|
killTree,
|
|
3560
|
+
kindFromStatus,
|
|
3305
3561
|
loadConfig,
|
|
3306
3562
|
logger,
|
|
3307
3563
|
newMessageId,
|
|
3308
3564
|
parseHello,
|
|
3309
3565
|
parsePlanJson,
|
|
3566
|
+
parseRetryAfter,
|
|
3310
3567
|
pipelineConfigSchema,
|
|
3311
3568
|
pipelineStepConfigSchema,
|
|
3312
3569
|
plannerConfigSchema,
|
|
3313
3570
|
projectConfigSchema,
|
|
3314
3571
|
providerConfigSchema,
|
|
3572
|
+
providerHttpError,
|
|
3573
|
+
providerTransportError,
|
|
3315
3574
|
registryKey,
|
|
3316
3575
|
requestJson,
|
|
3317
3576
|
requireCompletion,
|