@hilbras/omninode 2.0.0-alpha.1 → 2.0.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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({
@@ -151,7 +166,9 @@ var pipelineStepConfigSchema = z.object({
151
166
  model: z.string().optional(),
152
167
  depends_on: z.array(z.string()).optional(),
153
168
  retries: z.number().int().min(0).max(10).optional(),
154
- condition: z.enum(["always", "on-success", "on-failure"]).optional()
169
+ condition: z.enum(["always", "on-success", "on-failure"]).optional(),
170
+ /** Constraints passed to the planner for plan steps (§15). */
171
+ constraints: z.array(z.string()).optional()
155
172
  }).strict();
156
173
  var pipelineConfigSchema = z.object({
157
174
  id: z.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "pipeline id must be kebab-case"),
@@ -175,7 +192,9 @@ var projectConfigSchema = z.object({
175
192
  memory: z.object({
176
193
  provider: z.string().min(1).default("local"),
177
194
  base_url: z.string().url().optional(),
178
- api_key_env_var: z.string().min(1).optional()
195
+ api_key_env_var: z.string().min(1).optional(),
196
+ /** When true, memory failures fail tasks instead of being tolerated (§12). */
197
+ required: z.boolean().optional()
179
198
  }).strict().optional()
180
199
  }).strict();
181
200
  var appConfigSchema = z.object({
@@ -257,7 +276,8 @@ function toAppConfig(yaml) {
257
276
  memory: {
258
277
  provider: yaml.project.memory.provider,
259
278
  baseUrl: yaml.project.memory.base_url,
260
- apiKeyEnvVar: yaml.project.memory.api_key_env_var
279
+ apiKeyEnvVar: yaml.project.memory.api_key_env_var,
280
+ required: yaml.project.memory.required
261
281
  }
262
282
  } : {}
263
283
  },
@@ -278,7 +298,8 @@ function toPipelineDefinition(raw) {
278
298
  model: step.model,
279
299
  dependsOn: step.depends_on,
280
300
  retries: step.retries,
281
- condition: step.condition
301
+ condition: step.condition,
302
+ constraints: step.constraints
282
303
  }))
283
304
  };
284
305
  }
@@ -288,6 +309,7 @@ function toProviderConfig(raw) {
288
309
  type: raw.type,
289
310
  baseUrl: raw.base_url,
290
311
  apiKeyEnvVar: raw.api_key_env_var,
312
+ timeoutMs: raw.timeout_ms,
291
313
  headers: raw.headers,
292
314
  enabled: raw.enabled,
293
315
  metadata: raw.metadata
@@ -305,6 +327,11 @@ function toAgentConfig(raw) {
305
327
  inputMode: raw.input_mode,
306
328
  timeoutMs: raw.timeout_ms,
307
329
  inheritEnv: raw.inherit_env,
330
+ envPolicy: raw.env_policy,
331
+ envAllowlist: raw.env_allowlist,
332
+ envDenylist: raw.env_denylist,
333
+ maxOutputBytes: raw.max_output_bytes,
334
+ allowExternalCwd: raw.allow_external_cwd,
308
335
  metadata: raw.metadata
309
336
  };
310
337
  }
@@ -375,57 +402,200 @@ function authHeaders(config, env = process.env) {
375
402
  }
376
403
 
377
404
  // src/providers/http.ts
405
+ var HttpResponseError = class extends Error {
406
+ constructor(message, status, cause) {
407
+ super(message, { cause });
408
+ this.status = status;
409
+ this.name = "HttpResponseError";
410
+ }
411
+ status;
412
+ };
378
413
  var DEFAULT_TIMEOUT_MS = 3e4;
379
414
  async function requestJson(url, options = {}) {
380
415
  const { method = "GET", headers = {}, body, timeoutMs = DEFAULT_TIMEOUT_MS } = options;
381
- let response;
382
- try {
383
- response = await fetch(url, {
384
- method,
385
- headers: {
386
- accept: "application/json",
387
- ...body !== void 0 ? { "content-type": "application/json" } : {},
388
- ...headers
389
- },
390
- ...body !== void 0 ? { body: JSON.stringify(body) } : {},
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
- }
416
+ const response = await fetch(url, {
417
+ method,
418
+ headers: {
419
+ accept: "application/json",
420
+ ...body !== void 0 ? { "content-type": "application/json" } : {},
421
+ ...headers
422
+ },
423
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {},
424
+ signal: AbortSignal.timeout(timeoutMs)
425
+ });
399
426
  let parsed;
400
427
  try {
401
428
  parsed = await response.json();
402
429
  } catch (error) {
403
- throw new ProviderError("PROVIDER_UNAVAILABLE", `Response from ${url} was not valid JSON.`, {
404
- cause: error,
405
- details: { status: response.status }
406
- });
430
+ throw new HttpResponseError(`Response from ${url} was not valid JSON.`, response.status, error);
407
431
  }
408
432
  return { status: response.status, body: parsed };
409
433
  }
410
434
 
435
+ // src/providers/errors.ts
436
+ function kindToCode(kind) {
437
+ switch (kind) {
438
+ case "AUTHENTICATION_ERROR":
439
+ return "PROVIDER_AUTH_FAILED";
440
+ case "MODEL_NOT_FOUND":
441
+ return "MODEL_NOT_FOUND";
442
+ default:
443
+ return "PROVIDER_UNAVAILABLE";
444
+ }
445
+ }
446
+ function isRetryable(kind) {
447
+ return kind === "RATE_LIMIT_ERROR" || kind === "SERVER_ERROR" || kind === "NETWORK_ERROR";
448
+ }
449
+ function kindFromStatus(status) {
450
+ if (status === 401 || status === 403) return "AUTHENTICATION_ERROR";
451
+ if (status === 429) return "RATE_LIMIT_ERROR";
452
+ if (status === 404) return "MODEL_NOT_FOUND";
453
+ if (status === 408 || status === 504) return "TIMEOUT";
454
+ if (status >= 500) return "SERVER_ERROR";
455
+ if (status >= 400) return "INVALID_REQUEST";
456
+ return "UNKNOWN_ERROR";
457
+ }
458
+ function extractMessage(body) {
459
+ if (body === null || typeof body !== "object") return void 0;
460
+ const root = body;
461
+ const error = root.error ?? root;
462
+ const message = error.message ?? error.detail ?? root.message;
463
+ return typeof message === "string" ? message : void 0;
464
+ }
465
+ function parseRetryAfter(value, now = Date.now()) {
466
+ if (!value) return void 0;
467
+ const seconds = Number(value);
468
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
469
+ const date = Date.parse(value);
470
+ return Number.isNaN(date) ? void 0 : Math.max(0, date - now);
471
+ }
472
+ function isTimeoutError(error) {
473
+ return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError") || typeof error?.code === "string" && error.code === "ABORT_ERR";
474
+ }
475
+ function isNetworkError(error) {
476
+ return error instanceof TypeError || typeof error?.cause === "object" && error instanceof Error && !error.code;
477
+ }
478
+ function fallbackMessage(kind, context, status) {
479
+ const { provider, operation } = context;
480
+ switch (kind) {
481
+ case "AUTHENTICATION_ERROR":
482
+ return `Authentication with provider "${provider}" failed (HTTP ${status}). Check that the referenced API key is set and valid.`;
483
+ case "RATE_LIMIT_ERROR":
484
+ return `Provider "${provider}" rate-limited ${operation} (HTTP ${status}). Retry later.`;
485
+ case "MODEL_NOT_FOUND":
486
+ return `Provider "${provider}" does not expose the requested model (HTTP ${status}).`;
487
+ case "TIMEOUT":
488
+ return `Provider "${provider}" timed out during ${operation} (HTTP ${status}).`;
489
+ case "SERVER_ERROR":
490
+ return `Provider "${provider}" returned a server error during ${operation} (HTTP ${status}).`;
491
+ case "INVALID_REQUEST":
492
+ return `Provider "${provider}" rejected ${operation} as invalid (HTTP ${status}).`;
493
+ default:
494
+ return `Provider "${provider}" failed during ${operation} (HTTP ${status}).`;
495
+ }
496
+ }
497
+ function providerHttpError(context) {
498
+ const status = context.status ?? 0;
499
+ const kind = kindFromStatus(status);
500
+ const detail = extractMessage(context.body);
501
+ const retryAfterMs = context.retryAfterMs ?? parseRetryAfter(void 0);
502
+ return new ProviderError(
503
+ context.code ?? kindToCode(kind),
504
+ detail ?? fallbackMessage(kind, context, status),
505
+ {
506
+ cause: context.cause,
507
+ details: {
508
+ kind,
509
+ provider: context.provider,
510
+ operation: context.operation,
511
+ status,
512
+ retryable: isRetryable(kind),
513
+ ...retryAfterMs !== void 0 ? { retryAfterMs } : {},
514
+ ...detail ? { providerMessage: detail } : {}
515
+ }
516
+ }
517
+ );
518
+ }
519
+ function providerTransportError(context) {
520
+ const cause = context.cause;
521
+ const kind = isTimeoutError(cause) ? "TIMEOUT" : cause instanceof SyntaxError || cause?.name === "HttpResponseError" ? "UNKNOWN_ERROR" : "NETWORK_ERROR";
522
+ const message = context.cause instanceof Error ? context.cause.message : String(context.cause);
523
+ return new ProviderError("PROVIDER_UNAVAILABLE", `Provider request failed: ${message}`, {
524
+ cause: context.cause,
525
+ details: {
526
+ kind,
527
+ provider: context.provider,
528
+ operation: context.operation,
529
+ retryable: isRetryable(kind)
530
+ }
531
+ });
532
+ }
533
+
411
534
  // src/providers/openai-compatible/index.ts
535
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
412
536
  var OpenAICompatibleProvider = class {
413
537
  config;
538
+ providerId;
539
+ authentication;
414
540
  log;
415
- constructor(config, log = logger) {
541
+ /** Discovered models, so getModel() answers without another round-trip. */
542
+ discovered;
543
+ env;
544
+ constructor(config, log = logger, env = process.env) {
416
545
  this.config = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
546
+ this.env = env;
547
+ this.providerId = config.providerId ?? config.name;
548
+ this.authentication = {
549
+ method: config.apiKeyEnvVar ? "bearer" : "none",
550
+ ...config.apiKeyEnvVar ? { envVar: config.apiKeyEnvVar } : {},
551
+ configured: config.apiKeyEnvVar ? Boolean(env[config.apiKeyEnvVar]) : true
552
+ };
417
553
  this.log = log.child({ provider: config.name });
418
554
  }
419
- async listModels() {
420
- const { status, body } = await requestJson(`${this.config.baseUrl}/models`, {
421
- headers: authHeaders(this.config)
422
- });
423
- if (status === 401 || status === 403) {
424
- throw authFailed(this.config, status);
555
+ /** Provider-level request timeout (roadmap §6.6/§11) with the shared default. */
556
+ get requestTimeoutMs() {
557
+ return this.config.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
558
+ }
559
+ /** Authenticate + discover (roadmap §10, Model Discovery). */
560
+ async connect() {
561
+ authHeaders(this.config, this.env);
562
+ const models = await this.listModels();
563
+ this.discovered = models;
564
+ return models;
565
+ }
566
+ async getModel(modelId) {
567
+ const models = this.discovered ?? await this.connect();
568
+ return models.find((m) => m.id === modelId);
569
+ }
570
+ /** Raw /models body (protected so adapters can capture gateway-level metadata). */
571
+ async rawModelsResponse() {
572
+ const response = await this.modelsRequest();
573
+ return response.body;
574
+ }
575
+ async modelsRequest() {
576
+ let response;
577
+ try {
578
+ response = await requestJson(`${this.config.baseUrl}/models`, {
579
+ headers: authHeaders(this.config, this.env),
580
+ timeoutMs: this.requestTimeoutMs
581
+ });
582
+ } catch (error) {
583
+ if (error instanceof ProviderError) throw error;
584
+ throw providerTransportError({ provider: this.config.name, operation: "listModels", cause: error });
425
585
  }
586
+ const { status, body } = response;
426
587
  if (status !== 200) {
427
- throw unavailable(this.config, `Model discovery returned HTTP ${status}.`, status);
588
+ throw providerHttpError({
589
+ provider: this.config.name,
590
+ operation: "listModels",
591
+ status,
592
+ body
593
+ });
428
594
  }
595
+ return response;
596
+ }
597
+ async listModels() {
598
+ const body = await this.rawModelsResponse();
429
599
  const entries = extractModelEntries(body, this.config);
430
600
  const models = [];
431
601
  let skipped = 0;
@@ -434,22 +604,44 @@ var OpenAICompatibleProvider = class {
434
604
  skipped += 1;
435
605
  continue;
436
606
  }
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
- });
607
+ models.push(this.toModelInfo(entry));
447
608
  }
448
609
  if (skipped > 0) {
449
610
  this.log.warn(`Skipped ${skipped} model entr(y/ies) without a valid id.`);
450
611
  }
451
612
  return models;
452
613
  }
614
+ /** Maps a raw model entry onto the normalized metadata shape (§10). */
615
+ toModelInfo(entry) {
616
+ const parameters = Array.isArray(entry.supported_parameters) ? entry.supported_parameters.filter((p) => typeof p === "string") : void 0;
617
+ const contextWindow = typeof entry.context_length === "number" ? entry.context_length : typeof entry.context_window === "number" ? entry.context_window : void 0;
618
+ const inputTypes = normalizeModalities(entry.input_modalities);
619
+ const outputTypes = normalizeModalities(entry.output_modalities);
620
+ return {
621
+ provider: this.config.name,
622
+ id: entry.id,
623
+ displayName: entry.id,
624
+ ...typeof entry.name === "string" ? { name: entry.name } : {},
625
+ status: "available",
626
+ ...contextWindow !== void 0 ? { contextWindow } : {},
627
+ ...inputTypes !== void 0 ? { inputTypes } : {},
628
+ ...outputTypes !== void 0 ? { outputTypes } : {},
629
+ ...parameters !== void 0 ? {
630
+ supportsTools: parameters.includes("tools") || parameters.includes("tool_choice"),
631
+ supportsStructuredOutput: parameters.includes("response_format"),
632
+ supportsStreaming: parameters.includes("stream")
633
+ } : {},
634
+ metadata: {
635
+ ...typeof entry.owned_by === "string" ? { ownedBy: entry.owned_by } : {},
636
+ ...typeof entry.created === "number" ? { createdAt: entry.created } : {},
637
+ ...parameters !== void 0 ? { supportedParameters: parameters } : {}
638
+ }
639
+ };
640
+ }
641
+ /** Streaming where the gateway supports it (§11). */
642
+ stream(request, onChunk) {
643
+ return streamChatCompletion(this.config, request, onChunk, this.env);
644
+ }
453
645
  async healthCheck() {
454
646
  const lastCheckedAt = (/* @__PURE__ */ new Date()).toISOString();
455
647
  try {
@@ -479,27 +671,35 @@ var OpenAICompatibleProvider = class {
479
671
  }
480
672
  }
481
673
  async chat(request) {
482
- const { status, body } = await requestJson(`${this.config.baseUrl}/chat/completions`, {
483
- method: "POST",
484
- headers: authHeaders(this.config),
485
- body: {
486
- model: request.model,
487
- messages: request.messages,
488
- ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
489
- ...request.maxTokens !== void 0 ? { max_tokens: request.maxTokens } : {},
490
- ...request.stop !== void 0 ? { stop: request.stop } : {}
491
- }
492
- });
493
- if (status === 401 || status === 403) throw authFailed(this.config, status);
494
- if (status === 404) {
495
- throw new ProviderError(
496
- "MODEL_NOT_FOUND",
497
- `Model "${request.model}" or the chat endpoint was not found on provider "${this.config.name}" (HTTP 404).`,
498
- { details: { status } }
499
- );
674
+ let response;
675
+ try {
676
+ response = await requestJson(`${this.config.baseUrl}/chat/completions`, {
677
+ method: "POST",
678
+ headers: authHeaders(this.config, this.env),
679
+ timeoutMs: this.requestTimeoutMs,
680
+ body: {
681
+ model: request.model,
682
+ messages: request.messages,
683
+ ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
684
+ ...request.maxTokens !== void 0 ? { max_tokens: request.maxTokens } : {},
685
+ ...request.stop !== void 0 ? { stop: request.stop } : {}
686
+ }
687
+ });
688
+ } catch (error) {
689
+ if (error instanceof ProviderError) throw error;
690
+ throw providerTransportError({ provider: this.config.name, operation: "chat", cause: error });
500
691
  }
692
+ const { status, body } = response;
501
693
  if (status !== 200) {
502
- throw unavailable(this.config, `Chat completion returned HTTP ${status}.`, status);
694
+ throw providerHttpError({
695
+ provider: this.config.name,
696
+ operation: "chat",
697
+ status,
698
+ body,
699
+ ...status === 404 ? {
700
+ code: "MODEL_NOT_FOUND"
701
+ } : {}
702
+ });
503
703
  }
504
704
  const payload = body;
505
705
  const choices = Array.isArray(payload.choices) ? payload.choices : [];
@@ -521,6 +721,81 @@ var OpenAICompatibleProvider = class {
521
721
  };
522
722
  }
523
723
  };
724
+ async function streamChatCompletion(config, request, onChunk, env = process.env) {
725
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
726
+ const timeoutMs = config.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
727
+ let response;
728
+ try {
729
+ response = await fetch(`${baseUrl}/chat/completions`, {
730
+ method: "POST",
731
+ headers: {
732
+ accept: "text/event-stream",
733
+ "content-type": "application/json",
734
+ ...authHeaders(config, env)
735
+ },
736
+ body: JSON.stringify({
737
+ model: request.model,
738
+ messages: request.messages,
739
+ stream: true,
740
+ ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
741
+ ...request.maxTokens !== void 0 ? { max_tokens: request.maxTokens } : {}
742
+ }),
743
+ signal: AbortSignal.timeout(timeoutMs)
744
+ });
745
+ } catch (error) {
746
+ throw providerTransportError({ provider: config.name, operation: "stream", cause: error });
747
+ }
748
+ if (!response.ok) {
749
+ const body = await response.json().catch(() => void 0);
750
+ throw providerHttpError({ provider: config.name, operation: "stream", status: response.status, body });
751
+ }
752
+ if (!response.body) {
753
+ throw providerHttpError({
754
+ provider: config.name,
755
+ operation: "stream",
756
+ status: 500,
757
+ body: { error: { message: "streaming is not supported by this endpoint" } }
758
+ });
759
+ }
760
+ let content = "";
761
+ let model = request.model;
762
+ let finishReason;
763
+ const decoder = new TextDecoder();
764
+ let buffer = "";
765
+ for await (const piece of response.body) {
766
+ buffer += decoder.decode(piece, { stream: true });
767
+ let newline = buffer.indexOf("\n");
768
+ while (newline >= 0) {
769
+ const line = buffer.slice(0, newline).trim();
770
+ buffer = buffer.slice(newline + 1);
771
+ newline = buffer.indexOf("\n");
772
+ if (line.length === 0 || line.startsWith(":")) continue;
773
+ const data = line.startsWith("data:") ? line.slice(5).trim() : line;
774
+ if (data === "[DONE]") continue;
775
+ let event;
776
+ try {
777
+ event = JSON.parse(data);
778
+ } catch {
779
+ continue;
780
+ }
781
+ if (typeof event.model === "string") model = event.model;
782
+ const choices = Array.isArray(event.choices) ? event.choices : [];
783
+ const first = choices[0];
784
+ const delta = typeof first?.delta?.content === "string" ? first.delta.content : "";
785
+ if (delta.length > 0) content += delta;
786
+ if (typeof first?.finish_reason === "string") finishReason = first.finish_reason;
787
+ onChunk({ delta, ...finishReason !== void 0 ? { finishReason } : {}, raw: event });
788
+ }
789
+ }
790
+ return { model, content, ...finishReason !== void 0 ? { finishReason } : {} };
791
+ }
792
+ function normalizeModalities(value) {
793
+ const list = Array.isArray(value) ? value : typeof value === "string" ? [value] : void 0;
794
+ if (!list) return void 0;
795
+ const allowed = /* @__PURE__ */ new Set(["text", "image", "audio", "video", "embedding"]);
796
+ const modalities = list.filter((m) => typeof m === "string" && allowed.has(m));
797
+ return modalities.length > 0 ? modalities : void 0;
798
+ }
524
799
  function normalizeBaseUrl(baseUrl) {
525
800
  return baseUrl.replace(/\/+$/, "");
526
801
  }
@@ -535,14 +810,6 @@ function extractModelEntries(body, config) {
535
810
  { details: { endpoint: "models" } }
536
811
  );
537
812
  }
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
813
  function unavailable(config, message, status) {
547
814
  return new ProviderError("PROVIDER_UNAVAILABLE", `Provider "${config.name}": ${message}`, {
548
815
  details: { status }
@@ -555,11 +822,33 @@ function inferHealth(error) {
555
822
 
556
823
  // src/providers/omnihilbras/index.ts
557
824
  var OmniHilbrasProvider = class extends OpenAICompatibleProvider {
825
+ gateway;
826
+ async listModels() {
827
+ const raw = await this.rawModelsResponse();
828
+ await this.extractGatewayMetadata(raw);
829
+ const models = await super.listModels();
830
+ return this.gateway ? models.map((model) => ({ ...model, metadata: { ...model.metadata, gateway: this.gateway } })) : models;
831
+ }
558
832
  /**
559
833
  * Run the full connect flow. Throws on authentication or transport failure
560
834
  * (use healthCheck() for a non-throwing probe).
561
835
  */
562
- async connect(registry) {
836
+ /**
837
+ * Gateway metadata reported alongside the model list (gateway version, tier,
838
+ * region…), kept separate from per-model metadata (§11 — provider metadata).
839
+ */
840
+ gatewayMetadata() {
841
+ return this.gateway;
842
+ }
843
+ async extractGatewayMetadata(body) {
844
+ if (body !== null && typeof body === "object") {
845
+ const gateway = body.gateway;
846
+ if (gateway !== null && typeof gateway === "object") {
847
+ this.gateway = gateway;
848
+ }
849
+ }
850
+ }
851
+ async connectAndRegister(registry) {
563
852
  const apiKey = resolveApiKey(this.config);
564
853
  const discovered = await this.listModels();
565
854
  const seen = /* @__PURE__ */ new Set();
@@ -664,19 +953,431 @@ var ModelRegistry = class {
664
953
 
665
954
  // src/agents/process/index.ts
666
955
  import { spawn } from "child_process";
667
- var DEFAULT_TIMEOUT_MS2 = 6e5;
668
- var KILL_GRACE_MS = 5e3;
956
+
957
+ // src/agent-protocol/version.ts
958
+ var PROTOCOL_V2 = "omninode-agent-protocol/2";
959
+ var SUPPORTED_PROTOCOL_VERSIONS = [
960
+ "omninode-agent-protocol/1",
961
+ PROTOCOL_V2
962
+ ];
963
+ var MAX_MESSAGE_BYTES = 1048576;
964
+ function isSupportedProtocol(protocol) {
965
+ return SUPPORTED_PROTOCOL_VERSIONS.includes(protocol);
966
+ }
967
+
968
+ // src/agent-protocol/types.ts
969
+ function newMessageId(prefix = "msg") {
970
+ const random = Math.random().toString(36).slice(2, 10);
971
+ return `${prefix}-${Date.now().toString(36)}-${random}`;
972
+ }
973
+ function envelope(type, payload, ids = {}) {
974
+ return {
975
+ protocol: PROTOCOL_V2,
976
+ messageId: newMessageId(),
977
+ ...ids.requestId !== void 0 ? { requestId: ids.requestId } : {},
978
+ ...ids.taskId !== void 0 ? { taskId: ids.taskId } : {},
979
+ ...ids.agentId !== void 0 ? { agentId: ids.agentId } : {},
980
+ ...ids.pipelineId !== void 0 ? { pipelineId: ids.pipelineId } : {},
981
+ ...ids.executionId !== void 0 ? { executionId: ids.executionId } : {},
982
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
983
+ type,
984
+ payload,
985
+ ...ids.metadata !== void 0 ? { metadata: ids.metadata } : {}
986
+ };
987
+ }
988
+
989
+ // src/agent-protocol/codec.ts
990
+ var KNOWN_TYPES = /* @__PURE__ */ new Set([
991
+ // node → agent
992
+ "TASK",
993
+ "HELLO_ACK",
994
+ "CONTEXT",
995
+ "INSTRUCTION",
996
+ "RESPONSE",
997
+ "CANCEL",
998
+ // agent → node
999
+ "HELLO",
1000
+ "TASK_REQUEST",
1001
+ "TASK_ACCEPTED",
1002
+ "TASK_STARTED",
1003
+ "STATUS",
1004
+ "QUESTION",
1005
+ "REPORT",
1006
+ "ARTIFACT",
1007
+ "COMPLETION",
1008
+ "ERROR",
1009
+ "CANCELLED"
1010
+ ]);
1011
+ var PAYLOAD_REQUIRED_TYPES = /* @__PURE__ */ new Set(["TASK", "REPORT", "COMPLETION", "ERROR", "QUESTION", "ARTIFACT", "HELLO"]);
1012
+ function validateEnvelope(value, line) {
1013
+ const sample = line?.slice(0, 200);
1014
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1015
+ return { ok: false, error: { code: "NOT_AN_OBJECT", message: "message must be a JSON object", ...sample ? { sample } : {} } };
1016
+ }
1017
+ const raw = value;
1018
+ const protocol = raw.protocol;
1019
+ if (protocol === void 0) {
1020
+ const type = raw.type;
1021
+ if (typeof type !== "string" || !KNOWN_TYPES.has(type)) {
1022
+ return { ok: false, error: { code: "UNKNOWN_TYPE", message: `unknown message type ${String(type)}`, ...sample ? { sample } : {} } };
1023
+ }
1024
+ return {
1025
+ ok: true,
1026
+ legacy: true,
1027
+ envelope: {
1028
+ protocol: "omninode-agent-protocol/1",
1029
+ messageId: typeof raw.messageId === "string" ? raw.messageId : "legacy",
1030
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1031
+ type,
1032
+ payload: raw.payload
1033
+ }
1034
+ };
1035
+ }
1036
+ if (typeof protocol !== "string" || !isSupportedProtocol(protocol)) {
1037
+ return {
1038
+ ok: false,
1039
+ error: { code: "UNSUPPORTED_VERSION", message: `unsupported protocol "${String(protocol)}"`, ...sample ? { sample } : {} }
1040
+ };
1041
+ }
1042
+ if (raw.type === void 0) {
1043
+ return { ok: false, error: { code: "MISSING_FIELD", message: "message is missing 'type'", ...sample ? { sample } : {} } };
1044
+ }
1045
+ if (typeof raw.type !== "string" || !KNOWN_TYPES.has(raw.type)) {
1046
+ return { ok: false, error: { code: "UNKNOWN_TYPE", message: `unknown message type ${String(raw.type)}`, ...sample ? { sample } : {} } };
1047
+ }
1048
+ if (raw.timestamp !== void 0 && typeof raw.timestamp !== "string") {
1049
+ return { ok: false, error: { code: "MISSING_FIELD", message: "'timestamp' must be a string", ...sample ? { sample } : {} } };
1050
+ }
1051
+ if (PAYLOAD_REQUIRED_TYPES.has(raw.type) && (raw.payload === void 0 || raw.payload === null)) {
1052
+ return { ok: false, error: { code: "INVALID_PAYLOAD", message: `${raw.type} requires a payload`, ...sample ? { sample } : {} } };
1053
+ }
1054
+ return {
1055
+ ok: true,
1056
+ legacy: protocol !== PROTOCOL_V2,
1057
+ envelope: {
1058
+ protocol,
1059
+ messageId: typeof raw.messageId === "string" ? raw.messageId : "unknown",
1060
+ ...typeof raw.requestId === "string" ? { requestId: raw.requestId } : {},
1061
+ ...typeof raw.taskId === "string" ? { taskId: raw.taskId } : {},
1062
+ ...typeof raw.agentId === "string" ? { agentId: raw.agentId } : {},
1063
+ ...typeof raw.pipelineId === "string" ? { pipelineId: raw.pipelineId } : {},
1064
+ ...typeof raw.executionId === "string" ? { executionId: raw.executionId } : {},
1065
+ timestamp: typeof raw.timestamp === "string" ? raw.timestamp : (/* @__PURE__ */ new Date()).toISOString(),
1066
+ type: raw.type,
1067
+ payload: raw.payload,
1068
+ ...raw.metadata !== null && typeof raw.metadata === "object" ? { metadata: raw.metadata } : {}
1069
+ }
1070
+ };
1071
+ }
1072
+ function decodeMessage(line, maxBytes = MAX_MESSAGE_BYTES) {
1073
+ const trimmed = line.trim();
1074
+ if (trimmed.length === 0) {
1075
+ return { ok: false, error: { code: "INVALID_JSON", message: "empty line" } };
1076
+ }
1077
+ if (trimmed.length > maxBytes) {
1078
+ return {
1079
+ ok: false,
1080
+ error: { code: "OVERSIZED", message: `message of ${trimmed.length} bytes exceeds the ${maxBytes} byte limit`, sample: trimmed.slice(0, 200) }
1081
+ };
1082
+ }
1083
+ let value;
1084
+ try {
1085
+ value = JSON.parse(trimmed);
1086
+ } catch (error) {
1087
+ return {
1088
+ ok: false,
1089
+ error: { code: "INVALID_JSON", message: error instanceof Error ? error.message : "invalid JSON", sample: trimmed.slice(0, 200) }
1090
+ };
1091
+ }
1092
+ return validateEnvelope(value, trimmed);
1093
+ }
1094
+ function encodeMessage(envelope2, maxBytes = MAX_MESSAGE_BYTES) {
1095
+ const line = JSON.stringify(envelope2);
1096
+ if (line.length > maxBytes) {
1097
+ throw new Error(`outbound protocol message of ${line.length} bytes exceeds the ${maxBytes} byte limit`);
1098
+ }
1099
+ return line;
1100
+ }
1101
+ var ProtocolDecoder = class {
1102
+ buffer = "";
1103
+ maxBytes;
1104
+ constructor(maxBytes = MAX_MESSAGE_BYTES) {
1105
+ this.maxBytes = maxBytes;
1106
+ }
1107
+ push(chunk) {
1108
+ this.buffer += chunk;
1109
+ const results = [];
1110
+ let index = this.buffer.indexOf("\n");
1111
+ while (index >= 0) {
1112
+ const line = this.buffer.slice(0, index);
1113
+ this.buffer = this.buffer.slice(index + 1);
1114
+ if (line.trim().length > 0) results.push(decodeMessage(line, this.maxBytes));
1115
+ index = this.buffer.indexOf("\n");
1116
+ }
1117
+ if (this.buffer.length > this.maxBytes) {
1118
+ results.push({
1119
+ ok: false,
1120
+ error: { code: "OVERSIZED", message: `line exceeded ${this.maxBytes} bytes without a newline`, sample: this.buffer.slice(0, 200) }
1121
+ });
1122
+ this.buffer = "";
1123
+ }
1124
+ return results;
1125
+ }
1126
+ /** Flush any trailing line when the process exits. */
1127
+ flush() {
1128
+ if (this.buffer.trim().length === 0) {
1129
+ this.buffer = "";
1130
+ return [];
1131
+ }
1132
+ const result = decodeMessage(this.buffer, this.maxBytes);
1133
+ this.buffer = "";
1134
+ return [result];
1135
+ }
1136
+ };
1137
+
1138
+ // src/agent-protocol/handshake.ts
1139
+ function parseHello(envelopeValue) {
1140
+ if (envelopeValue.type !== "HELLO") {
1141
+ return { ok: false, reason: `expected HELLO, received ${envelopeValue.type}` };
1142
+ }
1143
+ const payload = envelopeValue.payload;
1144
+ if (payload === null || typeof payload !== "object") {
1145
+ return { ok: false, reason: "HELLO payload must be an object" };
1146
+ }
1147
+ const p = payload;
1148
+ if (typeof p.name !== "string" || p.name.length === 0) {
1149
+ return { ok: false, reason: "HELLO payload is missing 'name'" };
1150
+ }
1151
+ return {
1152
+ ok: true,
1153
+ descriptor: {
1154
+ protocol: envelopeValue.protocol,
1155
+ name: p.name,
1156
+ ...typeof p.version === "string" ? { version: p.version } : {},
1157
+ capabilities: Array.isArray(p.capabilities) ? p.capabilities.filter((c) => typeof c === "string") : [],
1158
+ supports: Array.isArray(p.supports) ? p.supports.filter((s) => typeof s === "string") : []
1159
+ }
1160
+ };
1161
+ }
1162
+ function buildHello(descriptor) {
1163
+ return envelope("HELLO", descriptor, { agentId: descriptor.name });
1164
+ }
1165
+ function buildHelloAck(descriptor) {
1166
+ return envelope("HELLO_ACK", {
1167
+ ok: true,
1168
+ protocol: PROTOCOL_V2,
1169
+ agent: descriptor.name,
1170
+ ...descriptor.version !== void 0 ? { agentVersion: descriptor.version } : {},
1171
+ supportedProtocols: [PROTOCOL_V2]
1172
+ }, { agentId: descriptor.name });
1173
+ }
1174
+
1175
+ // src/agent-protocol/session.ts
1176
+ var ProtocolSession = class {
1177
+ decoder;
1178
+ ids;
1179
+ responder;
1180
+ violations = [];
1181
+ questions = [];
1182
+ artifacts = [];
1183
+ reports = [];
1184
+ descriptor;
1185
+ completionSummary;
1186
+ errorMessage;
1187
+ constructor(ids, options = {}) {
1188
+ this.ids = ids;
1189
+ this.responder = options.responder;
1190
+ this.decoder = new ProtocolDecoder(options.maxBytes);
1191
+ }
1192
+ /** The v2 messages OmniNode sends to open a task (written to the agent's stdin). */
1193
+ taskMessages(objective, role, context, instruction) {
1194
+ const ids = this.ids;
1195
+ const messages = [envelope("TASK", { taskId: ids.taskId, objective }, ids)];
1196
+ if (role !== void 0) messages.push(envelope("CONTEXT", { role }, ids));
1197
+ if (context !== void 0 && context.length > 0) messages.push(envelope("CONTEXT", { body: context }, ids));
1198
+ if (instruction !== void 0 && instruction.length > 0) messages.push(envelope("INSTRUCTION", { text: instruction }, ids));
1199
+ return messages.map((m) => encodeMessage(m));
1200
+ }
1201
+ helloAck(descriptor) {
1202
+ return encodeMessage(buildHelloAck(descriptor));
1203
+ }
1204
+ /** Consumes decoded stdout, answering questions as they arrive. */
1205
+ async handle(decoded) {
1206
+ if (!decoded.ok) {
1207
+ this.violations.push(decoded.error);
1208
+ return;
1209
+ }
1210
+ const message = decoded.envelope;
1211
+ if (message.protocol === PROTOCOL_V2) this.sawV2 = true;
1212
+ if (message.type === "HELLO") {
1213
+ const hello = parseHello(message);
1214
+ if (hello.ok) this.descriptor = hello.descriptor;
1215
+ else this.violations.push({ code: "INVALID_PAYLOAD", message: `bad HELLO: ${hello.reason}` });
1216
+ return;
1217
+ }
1218
+ if (message.type === "QUESTION") {
1219
+ await this.answerQuestion(message);
1220
+ return;
1221
+ }
1222
+ if (message.type === "REPORT") {
1223
+ this.reports.push(message.payload);
1224
+ return;
1225
+ }
1226
+ if (message.type === "ARTIFACT") {
1227
+ this.artifacts.push(message.payload);
1228
+ return;
1229
+ }
1230
+ if (message.type === "COMPLETION") {
1231
+ const payload = message.payload;
1232
+ this.completionSummary = typeof payload?.summary === "string" ? payload.summary : "";
1233
+ return;
1234
+ }
1235
+ if (message.type === "ERROR") {
1236
+ const payload = message.payload;
1237
+ const detail = typeof payload?.message === "string" ? payload.message : "agent reported an error";
1238
+ const code = typeof payload?.code === "string" ? ` [${payload.code}]` : "";
1239
+ this.errorMessage = `Agent error${code}: ${detail}`;
1240
+ return;
1241
+ }
1242
+ if (message.type === "TASK_ACCEPTED" || message.type === "TASK_STARTED" || message.type === "STATUS") {
1243
+ return;
1244
+ }
1245
+ this.violations.push({
1246
+ code: "UNKNOWN_TYPE",
1247
+ message: `unexpected message type ${message.type} from agent`
1248
+ });
1249
+ }
1250
+ async answerQuestion(message) {
1251
+ const payload = message.payload ?? {};
1252
+ const questionId = typeof payload.questionId === "string" ? payload.questionId : message.messageId;
1253
+ const question = typeof payload.question === "string" ? payload.question : "";
1254
+ const expected = typeof payload.expected === "string" ? payload.expected : "text";
1255
+ let answer;
1256
+ if (this.responder) {
1257
+ try {
1258
+ answer = await this.responder({ questionId, question, expected });
1259
+ } catch (error) {
1260
+ this.violations.push({
1261
+ code: "INVALID_PAYLOAD",
1262
+ message: `question responder failed: ${error instanceof Error ? error.message : String(error)}`
1263
+ });
1264
+ }
1265
+ }
1266
+ this.questions.push({ questionId, question, ...answer !== void 0 ? { answer } : {} });
1267
+ this.pendingAnswers.push(
1268
+ encodeMessage(
1269
+ envelope("RESPONSE", { questionId, ...answer !== void 0 ? { body: answer } : {} }, {
1270
+ ...this.ids,
1271
+ requestId: message.requestId ?? this.ids.requestId
1272
+ })
1273
+ )
1274
+ );
1275
+ }
1276
+ pendingAnswers = [];
1277
+ /** True once any inbound message carried the v2 protocol field. */
1278
+ sawV2 = false;
1279
+ };
1280
+ async function decodeStream(session, chunk) {
1281
+ const decoded = session.decoder.push(chunk);
1282
+ for (const result of decoded) {
1283
+ await session.handle(result);
1284
+ }
1285
+ return session.pendingAnswers.splice(0);
1286
+ }
1287
+ function requireCompletion(session) {
1288
+ if (session.errorMessage !== void 0) {
1289
+ throw new ProtocolError(session.errorMessage);
1290
+ }
1291
+ }
1292
+
1293
+ // src/agents/cwd.ts
1294
+ import { statSync } from "fs";
1295
+ import { isAbsolute, relative, resolve } from "path";
1296
+ function validateWorkingDirectory(cwd, options = {}) {
1297
+ if (!cwd) return void 0;
1298
+ const normalized = isAbsolute(cwd) ? resolve(cwd) : resolve(process.cwd(), cwd);
1299
+ let stats;
1300
+ try {
1301
+ stats = statSync(normalized);
1302
+ } catch (error) {
1303
+ throw new AgentError(
1304
+ "AGENT_NOT_FOUND",
1305
+ `Working directory "${normalized}" does not exist or is not accessible.`,
1306
+ { cause: error, details: { cwd: normalized } }
1307
+ );
1308
+ }
1309
+ if (!stats.isDirectory()) {
1310
+ throw new AgentError("AGENT_NOT_FOUND", `Working directory "${normalized}" is not a directory.`, {
1311
+ details: { cwd: normalized }
1312
+ });
1313
+ }
1314
+ try {
1315
+ statSync(resolve(normalized, "."));
1316
+ } catch (error) {
1317
+ throw new AgentError(
1318
+ "AGENT_NOT_FOUND",
1319
+ `Working directory "${normalized}" is not accessible.`,
1320
+ { cause: error }
1321
+ );
1322
+ }
1323
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
1324
+ const rel = relative(projectRoot, normalized);
1325
+ const outsideProject = rel.startsWith("..") || isAbsolute(rel);
1326
+ if (outsideProject && options.allowExternal !== true) {
1327
+ throw new AgentError(
1328
+ "AGENT_NOT_FOUND",
1329
+ `Working directory "${normalized}" is outside the project root "${projectRoot}". Set allow_external_cwd: true on the agent to permit this.`,
1330
+ { details: { cwd: normalized, projectRoot } }
1331
+ );
1332
+ }
1333
+ return { cwd: normalized, outsideProject };
1334
+ }
1335
+
1336
+ // src/agents/env.ts
1337
+ var BASELINE_KEYS = ["PATH", "HOME"];
1338
+ function resolveEnvPolicy(config) {
1339
+ if (config.envPolicy !== void 0) return config.envPolicy;
1340
+ if (config.inheritEnv === false) return "explicit";
1341
+ return "inherit";
1342
+ }
669
1343
  function buildChildEnv(config) {
670
1344
  const configured = config.env ?? {};
671
- if (config.inheritEnv !== false) {
1345
+ const policy = resolveEnvPolicy(config);
1346
+ if (policy === "inherit") {
672
1347
  return { ...process.env, ...configured };
673
1348
  }
674
- return {
675
- PATH: process.env.PATH ?? "",
676
- HOME: process.env.HOME ?? "",
677
- ...configured
678
- };
1349
+ const base = {};
1350
+ for (const key of BASELINE_KEYS) {
1351
+ const value = process.env[key];
1352
+ if (value !== void 0) base[key] = value;
1353
+ }
1354
+ switch (policy) {
1355
+ case "explicit":
1356
+ return { ...base, ...configured };
1357
+ case "allowlist": {
1358
+ const allowed = { ...base };
1359
+ for (const key of config.envAllowlist ?? []) {
1360
+ const value = process.env[key];
1361
+ if (value !== void 0) allowed[key] = value;
1362
+ }
1363
+ return { ...allowed, ...configured };
1364
+ }
1365
+ case "denylist": {
1366
+ const denied = new Set((config.envDenylist ?? []).map((k) => k.toUpperCase()));
1367
+ const filtered = {};
1368
+ for (const [key, value] of Object.entries(process.env)) {
1369
+ if (denied.has(key.toUpperCase())) continue;
1370
+ filtered[key] = value;
1371
+ }
1372
+ return { ...filtered, ...configured };
1373
+ }
1374
+ }
679
1375
  }
1376
+
1377
+ // src/agents/process/index.ts
1378
+ var DEFAULT_TIMEOUT_MS2 = 6e5;
1379
+ var KILL_GRACE_MS = 5e3;
1380
+ var DEFAULT_MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
680
1381
  function composeTaskText(input) {
681
1382
  const sections = [`# Objective
682
1383
  ${input.objective}`];
@@ -697,18 +1398,19 @@ ${input.instruction}`);
697
1398
  return sections.join("\n\n");
698
1399
  }
699
1400
  function buildProtocolMessages(input, agentName) {
700
- const meta = {
1401
+ const session = new ProtocolSession({
1402
+ requestId: `req-${input.taskId}`,
701
1403
  taskId: input.taskId,
702
- agent: agentName,
703
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
704
- };
705
- const messages = [
706
- { type: "TASK", payload: { taskId: input.taskId, objective: input.objective } }
707
- ];
708
- if (input.role) messages.push({ type: "ROLE", payload: input.role });
709
- if (input.context) messages.push({ type: "CONTEXT", payload: { body: input.context } });
710
- if (input.instruction) messages.push({ type: "INSTRUCTION", payload: { text: input.instruction } });
711
- return messages.map((message) => JSON.stringify({ meta, message }));
1404
+ agentId: agentName,
1405
+ ...input.pipelineId !== void 0 ? { pipelineId: input.pipelineId } : {},
1406
+ ...input.executionId !== void 0 ? { executionId: input.executionId } : {}
1407
+ });
1408
+ return session.taskMessages(
1409
+ input.objective,
1410
+ input.role,
1411
+ input.context,
1412
+ input.instruction
1413
+ );
712
1414
  }
713
1415
  var ProcessAgent = class {
714
1416
  config;
@@ -737,40 +1439,73 @@ var ProcessAgent = class {
737
1439
  }
738
1440
  const mode = this.config.inputMode ?? "stdin";
739
1441
  const timeoutMs = this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
740
- return new Promise((resolve, reject) => {
1442
+ return new Promise((resolve2, reject) => {
1443
+ let cwd;
1444
+ try {
1445
+ cwd = validateWorkingDirectory(this.config.cwd, {
1446
+ allowExternal: this.config.allowExternalCwd
1447
+ })?.cwd;
1448
+ } catch (error) {
1449
+ reject(error);
1450
+ return;
1451
+ }
741
1452
  const child = spawn(
742
1453
  command,
743
1454
  [...this.config.args ?? [], ...mode === "arg" ? [composeTaskText(input)] : []],
744
1455
  {
745
- cwd: this.config.cwd,
1456
+ ...cwd !== void 0 ? { cwd } : {},
746
1457
  env: buildChildEnv(this.config),
747
- stdio: ["pipe", "pipe", "pipe"]
1458
+ stdio: ["pipe", "pipe", "pipe"],
1459
+ // Own process group so the whole tree can be terminated together
1460
+ // (roadmap §8: no orphaned children).
1461
+ ...process.platform === "win32" ? {} : { detached: true }
748
1462
  }
749
1463
  );
750
1464
  this.running.set(input.taskId, child);
1465
+ GLOBAL_RUNNING.add(child);
1466
+ installSignalForwarding();
751
1467
  let stdout = "";
752
1468
  let stderr = "";
753
1469
  let timedOut = false;
1470
+ let outputLimitExceeded = false;
754
1471
  let settled = false;
1472
+ const maxOutputBytes = this.config.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
755
1473
  const timer = setTimeout(() => {
756
1474
  timedOut = true;
757
- this.log.warn(`Task ${input.taskId} exceeded ${timeoutMs}ms \u2014 killing process.`);
758
- child.kill("SIGTERM");
759
- setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
1475
+ this.log.warn(`Task ${input.taskId} exceeded ${timeoutMs}ms \u2014 killing process tree.`);
1476
+ killTree(child, "SIGTERM");
1477
+ setTimeout(() => killTree(child, "SIGKILL"), KILL_GRACE_MS).unref();
760
1478
  }, timeoutMs);
761
1479
  const settle = (finish) => {
762
1480
  if (settled) return;
763
1481
  settled = true;
764
1482
  clearTimeout(timer);
765
1483
  this.running.delete(input.taskId);
1484
+ GLOBAL_RUNNING.delete(child);
766
1485
  finish();
767
1486
  };
768
- child.stdout?.on("data", (chunk) => {
769
- stdout += chunk.toString();
770
- });
771
- child.stderr?.on("data", (chunk) => {
772
- stderr += chunk.toString();
773
- });
1487
+ const collect = (into, chunk) => {
1488
+ if (outputLimitExceeded) return;
1489
+ const text = sanitizeChunk(chunk);
1490
+ if (into === "stdout") {
1491
+ if (stdout.length + text.length > maxOutputBytes) {
1492
+ stdout = (stdout + text).slice(0, maxOutputBytes);
1493
+ outputLimitExceeded = true;
1494
+ this.log.warn(`Agent "${this.config.name}" exceeded the ${maxOutputBytes} byte output limit \u2014 killing it.`);
1495
+ killTree(child, "SIGKILL");
1496
+ return;
1497
+ }
1498
+ stdout += text;
1499
+ } else {
1500
+ if (stderr.length + text.length > maxOutputBytes) {
1501
+ stderr = (stderr + text).slice(0, maxOutputBytes);
1502
+ return;
1503
+ }
1504
+ stderr += text;
1505
+ }
1506
+ };
1507
+ child.stdout?.on("data", (chunk) => collect("stdout", chunk));
1508
+ child.stderr?.on("data", (chunk) => collect("stderr", chunk));
774
1509
  child.stdin?.on("error", () => {
775
1510
  });
776
1511
  if (mode !== "arg") {
@@ -804,7 +1539,19 @@ var ProcessAgent = class {
804
1539
  new AgentError(
805
1540
  "AGENT_TIMEOUT",
806
1541
  `Agent "${this.config.name}" timed out after ${timeoutMs}ms and was killed.`,
807
- { details: { taskId: input.taskId, stdout: stdout.slice(0, 4e3), stderr: stderr.slice(0, 2e3) } }
1542
+ {
1543
+ details: {
1544
+ operation: "agent.run",
1545
+ agent: this.config.name,
1546
+ durationMs: timeoutMs,
1547
+ // The process was started and may have performed side
1548
+ // effects before being killed — the outcome is unknown,
1549
+ // not failed (roadmap §6.3).
1550
+ dispatched: true,
1551
+ stdout: stdout.slice(0, 4e3),
1552
+ stderr: stderr.slice(0, 2e3)
1553
+ }
1554
+ }
808
1555
  )
809
1556
  );
810
1557
  return;
@@ -812,7 +1559,7 @@ var ProcessAgent = class {
812
1559
  const logs = stderr.trim().length > 0 ? stderr.split("\n") : void 0;
813
1560
  if (mode === "protocol") {
814
1561
  try {
815
- resolve(
1562
+ resolve2(
816
1563
  interpretProtocolRun({
817
1564
  input,
818
1565
  agentName: this.config.name,
@@ -827,14 +1574,14 @@ var ProcessAgent = class {
827
1574
  return;
828
1575
  }
829
1576
  const trimmed = stdout.trim();
830
- resolve({
1577
+ resolve2({
831
1578
  taskId: input.taskId,
832
- status: code === 0 ? "completed" : "failed",
1579
+ status: code === 0 && !outputLimitExceeded ? "completed" : "failed",
833
1580
  summary: trimmed.length > 0 ? firstLine(trimmed) : void 0,
834
1581
  rawOutput: trimmed.length > 0 ? trimmed : void 0,
835
1582
  exitCode: code ?? void 0,
836
1583
  logs,
837
- error: code === 0 ? void 0 : `Process exited with code ${code ?? "unknown"}${signal ? ` (signal: ${signal})` : ""}`
1584
+ ...outputLimitExceeded ? { error: `Agent exceeded the ${maxOutputBytes} byte output limit and was terminated.` } : code === 0 ? {} : { error: describeExit(code, signal) }
838
1585
  });
839
1586
  });
840
1587
  });
@@ -843,67 +1590,117 @@ var ProcessAgent = class {
843
1590
  /** Ask the agent to stop current work (SIGTERM). */
844
1591
  async cancel(taskId) {
845
1592
  const child = this.running.get(taskId);
846
- if (child) child.kill("SIGTERM");
1593
+ if (child) killTree(child, "SIGTERM");
847
1594
  }
848
1595
  runningTaskIds() {
849
1596
  return [...this.running.keys()];
850
1597
  }
851
1598
  };
852
- function interpretProtocolRun({ input, agentName, stdout, exitCode, logs }) {
853
- const reports = [];
854
- let summary;
855
- let error;
856
- let sawProtocolMessage = false;
857
- for (const line of stdout.split("\n")) {
858
- const trimmed2 = line.trim();
859
- if (trimmed2.length === 0) continue;
860
- const message = parseAgentMessage(trimmed2);
861
- if (!message) continue;
862
- sawProtocolMessage = true;
863
- if (message.type === "REPORT") {
864
- const report = normalizeReport(message.payload, input.taskId, agentName, reports.length);
865
- if (report) reports.push(report);
866
- } else if (message.type === "COMPLETION") {
867
- const payload = message.payload;
868
- if (typeof payload?.summary === "string") summary = payload.summary;
869
- } else if (message.type === "ERROR") {
870
- const payload = message.payload;
871
- const detail = typeof payload?.message === "string" ? payload.message : "unknown error";
872
- const code = typeof payload?.code === "string" ? ` [${payload.code}]` : "";
873
- error = `Agent error${code}: ${detail}`;
874
- }
1599
+ function interpretProtocolRun({
1600
+ input,
1601
+ agentName,
1602
+ stdout,
1603
+ exitCode,
1604
+ logs,
1605
+ responder
1606
+ }) {
1607
+ const session = new ProtocolSession(
1608
+ {
1609
+ requestId: `req-${input.taskId}`,
1610
+ taskId: input.taskId,
1611
+ agentId: agentName,
1612
+ ...input.pipelineId !== void 0 ? { pipelineId: input.pipelineId } : {},
1613
+ ...input.executionId !== void 0 ? { executionId: input.executionId } : {}
1614
+ },
1615
+ { ...responder !== void 0 ? { responder } : {} }
1616
+ );
1617
+ for (const result of session.decoder.push(stdout)) {
1618
+ session.handle(result);
875
1619
  }
876
- if (!sawProtocolMessage && exitCode === 0) {
877
- throw new ProtocolError(
878
- `Agent "${agentName}" produced no protocol messages on stdout (input mode "protocol").`,
879
- { details: { taskId: input.taskId, stdout: stdout.slice(0, 2e3) } }
880
- );
1620
+ for (const result of session.decoder.flush()) {
1621
+ session.handle(result);
881
1622
  }
1623
+ 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
1624
  const trimmed = stdout.trim();
1625
+ const protocolBlock = {
1626
+ protocol: session.descriptor?.protocol ?? PROTOCOL_V2,
1627
+ legacy: !session.sawV2 && session.descriptor === void 0,
1628
+ ...session.descriptor !== void 0 ? { descriptor: session.descriptor } : {},
1629
+ questions: session.questions,
1630
+ violations: session.violations.map((v) => `${v.code}: ${v.message}`)
1631
+ };
1632
+ if (session.errorMessage !== void 0) {
1633
+ return {
1634
+ taskId: input.taskId,
1635
+ status: "failed",
1636
+ error: session.errorMessage,
1637
+ rawOutput: trimmed.length > 0 ? trimmed : void 0,
1638
+ exitCode: exitCode ?? void 0,
1639
+ logs,
1640
+ protocol: protocolBlock,
1641
+ ...reports.length > 0 ? { reports } : {}
1642
+ };
1643
+ }
1644
+ if (session.descriptor === void 0 && session.violations.length > 0 && session.reports.length === 0 && session.completionSummary === void 0) {
1645
+ return {
1646
+ taskId: input.taskId,
1647
+ status: "failed",
1648
+ error: `agent produced no valid protocol messages: ${protocolBlock.violations.join("; ")}`,
1649
+ rawOutput: trimmed.length > 0 ? trimmed : void 0,
1650
+ exitCode: exitCode ?? void 0,
1651
+ logs,
1652
+ protocol: protocolBlock
1653
+ };
1654
+ }
1655
+ const completed = exitCode === 0 && session.completionSummary !== void 0;
883
1656
  return {
884
1657
  taskId: input.taskId,
885
- status: error !== void 0 || exitCode !== 0 ? "failed" : "completed",
886
- summary: summary ?? reports[0]?.summary,
1658
+ status: completed || exitCode === 0 ? completed ? "completed" : "completed" : "failed",
1659
+ summary: session.completionSummary ?? reports[0]?.summary,
887
1660
  rawOutput: trimmed.length > 0 ? trimmed : void 0,
888
1661
  exitCode: exitCode ?? void 0,
889
1662
  logs,
890
- reports: reports.length > 0 ? reports : void 0,
891
- error: error ?? (exitCode !== 0 ? `Process exited with code ${exitCode ?? "unknown"}` : void 0)
1663
+ protocol: protocolBlock,
1664
+ ...reports.length > 0 ? { reports } : {},
1665
+ ...exitCode !== 0 ? { error: `Process exited with code ${exitCode ?? "unknown"}` } : {}
892
1666
  };
893
1667
  }
894
- function parseAgentMessage(line) {
895
- try {
896
- const parsed = JSON.parse(line);
897
- if (typeof parsed.type === "string") {
898
- return { type: parsed.type, payload: parsed.payload };
899
- }
900
- const inner = parsed.message;
901
- if (inner && typeof inner.type === "string") {
902
- return { type: inner.type, payload: inner.payload };
1668
+ var GLOBAL_RUNNING = /* @__PURE__ */ new Set();
1669
+ var signalForwardingInstalled = false;
1670
+ function installSignalForwarding() {
1671
+ if (signalForwardingInstalled) return;
1672
+ signalForwardingInstalled = true;
1673
+ for (const signal of ["SIGINT", "SIGTERM"]) {
1674
+ process.on(signal, () => {
1675
+ for (const child of GLOBAL_RUNNING) {
1676
+ killTree(child, "SIGTERM");
1677
+ }
1678
+ });
1679
+ }
1680
+ }
1681
+ function killTree(child, signal) {
1682
+ if (child.pid === void 0) return;
1683
+ if (process.platform !== "win32") {
1684
+ try {
1685
+ process.kill(-child.pid, signal);
1686
+ return;
1687
+ } catch {
903
1688
  }
1689
+ }
1690
+ try {
1691
+ child.kill(signal);
904
1692
  } catch {
905
1693
  }
906
- return void 0;
1694
+ }
1695
+ function describeExit(code, signal) {
1696
+ if (signal !== null) {
1697
+ return signal === "SIGKILL" ? "Process was killed (SIGKILL) \u2014 usually an external kill or OOM." : `Process terminated by signal ${signal}.`;
1698
+ }
1699
+ if (code === null) return "Process terminated without an exit code.";
1700
+ return `Process exited with code ${code}.`;
1701
+ }
1702
+ function sanitizeChunk(chunk) {
1703
+ return [...chunk.toString("utf8")].filter((char) => char.charCodeAt(0) !== 0).join("");
907
1704
  }
908
1705
  function normalizeReport(payload, taskId, agentName, index) {
909
1706
  if (payload === null || typeof payload !== "object") return void 0;
@@ -924,6 +1721,40 @@ function firstLine(text, maxLength = 200) {
924
1721
  return line.length > maxLength ? `${line.slice(0, maxLength)}\u2026` : line;
925
1722
  }
926
1723
 
1724
+ // src/agents/adapters.ts
1725
+ var AgentAdapterRegistry = class {
1726
+ adapters = /* @__PURE__ */ new Map();
1727
+ register(adapter) {
1728
+ this.adapters.set(adapter.integration, adapter);
1729
+ }
1730
+ get(integration) {
1731
+ return this.adapters.get(integration);
1732
+ }
1733
+ list() {
1734
+ return [...this.adapters.values()];
1735
+ }
1736
+ create(config) {
1737
+ const adapter = this.get(config.integration);
1738
+ if (!adapter) {
1739
+ throw new AgentError(
1740
+ "AGENT_NOT_FOUND",
1741
+ `No agent adapter registered for integration "${config.integration}". Registered: ${[...this.adapters.keys()].join(", ") || "(none)"}.`
1742
+ );
1743
+ }
1744
+ return adapter.create(config);
1745
+ }
1746
+ };
1747
+ var ProcessAgentAdapter = class {
1748
+ constructor(factory) {
1749
+ this.factory = factory;
1750
+ }
1751
+ factory;
1752
+ integration = "process";
1753
+ create(config) {
1754
+ return this.factory(config);
1755
+ }
1756
+ };
1757
+
927
1758
  // src/agents/registry.ts
928
1759
  var AgentRegistry = class {
929
1760
  agents = /* @__PURE__ */ new Map();
@@ -939,14 +1770,15 @@ var AgentRegistry = class {
939
1770
  };
940
1771
 
941
1772
  // src/agents/factory.ts
942
- function createAgent(config) {
943
- if (config.integration === "process") {
944
- return new ProcessAgent(config);
945
- }
1773
+ var defaultAgentAdapters = new AgentAdapterRegistry();
1774
+ defaultAgentAdapters.register(new ProcessAgentAdapter((config) => new ProcessAgent(config)));
1775
+ function createAgent(config, adapters = defaultAgentAdapters) {
1776
+ const registered = adapters.get(config.integration);
1777
+ if (registered) return registered.create(config);
946
1778
  if (config.integration === "native") {
947
1779
  throw new OmniNodeError(
948
1780
  "NOT_IMPLEMENTED",
949
- `Native agent adapters are planned for a later phase. Use input over stdin/stdout (type "cli") for "${config.name}" for now.`
1781
+ `No native adapter is registered for "${config.name}". Register an IAgentAdapter (createAgent(config, adapters)) or use input over stdin/stdout (type "cli").`
950
1782
  );
951
1783
  }
952
1784
  throw new OmniNodeError("NOT_IMPLEMENTED", `Agent integration "${config.integration}" has no adapter yet.`);
@@ -1082,12 +1914,18 @@ import { randomBytes } from "crypto";
1082
1914
  var ALLOWED_TRANSITIONS = {
1083
1915
  created: ["queued", "running", "cancelled"],
1084
1916
  queued: ["running", "cancelled"],
1085
- running: ["completed", "failed", "cancelled"],
1917
+ running: ["completed", "failed", "timed_out", "unknown", "cancelled", "partially_completed"],
1086
1918
  waiting: ["running", "cancelled"],
1087
1919
  completed: [],
1088
- failed: [],
1089
- cancelled: []
1920
+ // failed → running is the only re-entry: bounded automatic retry (§6.4).
1921
+ failed: ["running"],
1922
+ timed_out: [],
1923
+ cancelled: [],
1924
+ unknown: [],
1925
+ partially_completed: []
1090
1926
  };
1927
+ var RETRYABLE_ERROR_CODES = ["AGENT_FAILED"];
1928
+ var EXECUTION_HISTORY_LIMIT = 10;
1091
1929
  var TaskEngine = class {
1092
1930
  constructor(options) {
1093
1931
  this.options = options;
@@ -1119,6 +1957,7 @@ var TaskEngine = class {
1119
1957
  id: generateTaskId(),
1120
1958
  objective: input.objective.trim(),
1121
1959
  status: "created",
1960
+ attempt: 0,
1122
1961
  createdAt: now,
1123
1962
  updatedAt: now,
1124
1963
  ...input.project !== void 0 ? { project: input.project } : {},
@@ -1135,8 +1974,10 @@ var TaskEngine = class {
1135
1974
  const task = await this.mustGet(id);
1136
1975
  return this.transition(task, "queued");
1137
1976
  }
1138
- /** Runs the task through its assigned agent and records the result. */
1139
- async run(id) {
1977
+ /** Runs the task through its assigned agent and records the result.
1978
+ * `maxAttempts` enables bounded automatic retries of *retryable* errors
1979
+ * with linear backoff; unknown/timed_out outcomes are never auto-retried. */
1980
+ async run(id, options = {}) {
1140
1981
  const task = await this.mustGet(id);
1141
1982
  if (task.status !== "created" && task.status !== "queued") {
1142
1983
  throw new TaskError("TASK_INVALID", `Task ${id} is "${task.status}" and cannot be run.`);
@@ -1149,49 +1990,89 @@ var TaskEngine = class {
1149
1990
  throw new TaskError("TASK_INVALID", `Agent "${task.agent}" is not registered anymore.`);
1150
1991
  }
1151
1992
  const role = task.role ? this.options.roles.get(task.role) : void 0;
1152
- const running = await this.transition(task, "running");
1153
1993
  this.log.info(`Running task ${task.id} on agent "${task.agent}".`);
1154
- let background = running.context?.background;
1994
+ let background = task.context?.background;
1155
1995
  if (this.options.memory) {
1156
1996
  const memoryContext = await this.options.memory.gatherContext({
1157
- objective: running.objective,
1158
- ...running.role ? { role: running.role } : {},
1159
- ...running.project ? { project: running.project } : {}
1997
+ objective: task.objective,
1998
+ ...task.role ? { role: task.role } : {},
1999
+ ...task.project ? { project: task.project } : {}
1160
2000
  });
1161
2001
  if (memoryContext.length > 0) {
1162
2002
  background = [memoryContext, background].filter((part) => part && part.length > 0).join("\n\n");
1163
2003
  }
1164
2004
  }
1165
- let output;
1166
- try {
1167
- output = await agent.run({
1168
- taskId: running.id,
1169
- objective: running.objective,
1170
- ...role ? { role } : {},
1171
- ...background !== void 0 ? { context: background } : {}
1172
- });
1173
- } catch (error) {
1174
- const message = error instanceof Error ? error.message : String(error);
1175
- this.log.error(`Task ${task.id} failed: ${message}`);
1176
- return this.transition(running, "failed", {
1177
- result: { error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }
2005
+ const maxAttempts = Math.max(1, options.maxAttempts ?? 1);
2006
+ const backoffMs = options.retryBackoffMs ?? 0;
2007
+ let current = task;
2008
+ for (let i = 0; i < maxAttempts; i += 1) {
2009
+ const attempt = current.attempt + 1;
2010
+ const executionId = `exec-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
2011
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
2012
+ current = await this.transition(current, "running", { attempt });
2013
+ let output;
2014
+ let thrown;
2015
+ try {
2016
+ output = await agent.run({
2017
+ taskId: current.id,
2018
+ objective: current.objective,
2019
+ ...role ? { role } : {},
2020
+ ...background !== void 0 ? { context: background } : {}
2021
+ });
2022
+ } catch (error) {
2023
+ thrown = error;
2024
+ }
2025
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2026
+ if (thrown !== void 0 || output === void 0) {
2027
+ const outcome = classifyThrown(thrown);
2028
+ const message = thrown instanceof Error ? thrown.message : String(thrown);
2029
+ const retryable = outcome === "failed" && RETRYABLE_ERROR_CODES.includes(errorCode(thrown)) && i < maxAttempts - 1;
2030
+ current = await this.transition(current, outcome, {
2031
+ attempt,
2032
+ lastError: message,
2033
+ execution: {
2034
+ executionId,
2035
+ attempt,
2036
+ startedAt,
2037
+ finishedAt,
2038
+ outcome,
2039
+ error: message
2040
+ }
2041
+ });
2042
+ this.log.warn(`Task ${current.id} attempt ${attempt} \u2192 ${outcome}: ${message}`);
2043
+ if (retryable) {
2044
+ if (backoffMs > 0) await sleep(backoffMs * attempt);
2045
+ continue;
2046
+ }
2047
+ if (this.options.memory) await this.options.memory.recordTaskOutcome(current);
2048
+ return current;
2049
+ }
2050
+ const currentState = await this.options.store.get(current.id);
2051
+ if (currentState?.status === "cancelled") {
2052
+ current = currentState;
2053
+ break;
2054
+ }
2055
+ const result = {
2056
+ ...output.summary !== void 0 ? { summary: output.summary } : {},
2057
+ ...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
2058
+ // Keep the verbatim output (bounded) for report extraction and audit.
2059
+ ...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
2060
+ ...output.error !== void 0 ? { error: output.error } : {},
2061
+ finishedAt
2062
+ };
2063
+ const finalStatus = output.status === "completed" ? "completed" : "failed";
2064
+ current = await this.transition(current, finalStatus, {
2065
+ attempt,
2066
+ result,
2067
+ execution: { executionId, attempt, startedAt, finishedAt, outcome: finalStatus }
1178
2068
  });
2069
+ this.log.info(`Task ${current.id} finished: ${finalStatus}.`);
2070
+ break;
1179
2071
  }
1180
- const result = {
1181
- ...output.summary !== void 0 ? { summary: output.summary } : {},
1182
- ...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
1183
- // Keep the verbatim output (bounded) for report extraction and audit.
1184
- ...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
1185
- ...output.error !== void 0 ? { error: output.error } : {},
1186
- finishedAt: (/* @__PURE__ */ new Date()).toISOString()
1187
- };
1188
- const finalStatus = output.status === "completed" ? "completed" : "failed";
1189
- const finished = await this.transition(running, finalStatus, { result });
1190
- this.log.info(`Task ${task.id} finished: ${finalStatus}.`);
1191
2072
  if (this.options.memory) {
1192
- await this.options.memory.recordTaskOutcome(finished);
2073
+ await this.options.memory.recordTaskOutcome(current);
1193
2074
  }
1194
- return finished;
2075
+ return current;
1195
2076
  }
1196
2077
  async cancel(id) {
1197
2078
  const task = await this.mustGet(id);
@@ -1204,13 +2085,14 @@ var TaskEngine = class {
1204
2085
  }
1205
2086
  return this.transition(task, "cancelled");
1206
2087
  }
1207
- /** Error recovery (§27 Phase 9): reset a failed/cancelled task so it can run again. */
2088
+ /** Error recovery: reset a failed/cancelled/unknown/timed_out task so it can run again. */
1208
2089
  async retry(id) {
1209
2090
  const task = await this.mustGet(id);
1210
- if (task.status !== "failed" && task.status !== "cancelled") {
2091
+ const retryableStates = ["failed", "cancelled", "unknown", "timed_out"];
2092
+ if (!retryableStates.includes(task.status)) {
1211
2093
  throw new TaskError(
1212
2094
  "TASK_INVALID",
1213
- `Only failed or cancelled tasks can be retried \u2014 task ${id} is "${task.status}".`
2095
+ `Only failed, timed out, unknown or cancelled tasks can be retried \u2014 task ${id} is "${task.status}".`
1214
2096
  );
1215
2097
  }
1216
2098
  const { result: _discarded, ...rest } = task;
@@ -1242,10 +2124,14 @@ var TaskEngine = class {
1242
2124
  if (!allowed.includes(to)) {
1243
2125
  throw new TaskError("TASK_INVALID", `Task ${task.id} cannot move from "${task.status}" to "${to}".`);
1244
2126
  }
2127
+ const executions = extra.execution ? [...task.executions ?? [], extra.execution].slice(-EXECUTION_HISTORY_LIMIT) : task.executions;
1245
2128
  const updated = {
1246
2129
  ...task,
1247
2130
  status: to,
1248
2131
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2132
+ ...extra.attempt !== void 0 ? { attempt: extra.attempt } : {},
2133
+ ...extra.lastError !== void 0 ? { lastError: extra.lastError } : {},
2134
+ ...executions !== void 0 ? { executions } : {},
1249
2135
  ...extra.result !== void 0 ? { result: { ...task.result, ...extra.result } } : {}
1250
2136
  };
1251
2137
  await this.options.store.save(updated);
@@ -1266,6 +2152,19 @@ var TaskEngine = class {
1266
2152
  function generateTaskId() {
1267
2153
  return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
1268
2154
  }
2155
+ function errorCode(error) {
2156
+ return error?.code ?? "";
2157
+ }
2158
+ function classifyThrown(error) {
2159
+ if (error instanceof AgentError && error.code === "AGENT_TIMEOUT") {
2160
+ const dispatched = error.details?.dispatched;
2161
+ return dispatched === false ? "timed_out" : "unknown";
2162
+ }
2163
+ return "failed";
2164
+ }
2165
+ function sleep(ms) {
2166
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
2167
+ }
1269
2168
 
1270
2169
  // src/audit/index.ts
1271
2170
  import { appendFile, mkdir as mkdir2 } from "fs/promises";
@@ -1308,6 +2207,23 @@ var FileAuditLog = class {
1308
2207
  }
1309
2208
  };
1310
2209
 
2210
+ // src/memory/base.ts
2211
+ function defaultSearch(provider, text, options = {}) {
2212
+ return provider.retrieve({
2213
+ text,
2214
+ ...options.scope !== void 0 ? { scope: options.scope } : {},
2215
+ ...options.limit !== void 0 ? { limit: options.limit } : {}
2216
+ });
2217
+ }
2218
+ function defaultMetadata(name, options = {}) {
2219
+ return {
2220
+ name,
2221
+ capabilities: options.capabilities ?? ["retrieve", "store", "search"],
2222
+ relevanceRanking: options.relevanceRanking ?? false,
2223
+ ...options.backend !== void 0 ? { backend: options.backend } : {}
2224
+ };
2225
+ }
2226
+
1311
2227
  // src/memory/local.ts
1312
2228
  var STOPWORDS = /* @__PURE__ */ new Set([
1313
2229
  "the",
@@ -1370,9 +2286,27 @@ var LocalMemoryProvider = class extends JsonFileStore {
1370
2286
  constructor(directory) {
1371
2287
  super({ ...directory !== void 0 ? { directory } : {}, fileName: "memory.json" });
1372
2288
  }
1373
- async query(query) {
2289
+ metadata() {
2290
+ return defaultMetadata(this.name, {
2291
+ capabilities: ["retrieve", "store", "search", "categories", "tags"],
2292
+ relevanceRanking: true,
2293
+ backend: { kind: "json-file", deterministicScoring: true }
2294
+ });
2295
+ }
2296
+ search(text, options = {}) {
2297
+ return defaultSearch(this, text, options);
2298
+ }
2299
+ /** @deprecated v1 alias of {@link retrieve}. */
2300
+ query(query) {
2301
+ return this.retrieve(query);
2302
+ }
2303
+ /** @deprecated v1 alias of {@link store}. */
2304
+ write(entry) {
2305
+ return this.store(entry);
2306
+ }
2307
+ async retrieve(query) {
1374
2308
  const entries = await this.readAll();
1375
- const filtered = entries.filter((entry) => query.scope ? entry.scope === query.scope : true).filter(
2309
+ const filtered = entries.filter((entry) => query.scope ? entry.scope === query.scope : true).filter((entry) => query.category ? entry.category === query.category : true).filter(
1376
2310
  (entry) => query.tags && query.tags.length > 0 ? query.tags.every((tag) => (entry.tags ?? []).includes(tag)) : true
1377
2311
  );
1378
2312
  if (!query.text || query.text.trim().length === 0) {
@@ -1381,7 +2315,7 @@ var LocalMemoryProvider = class extends JsonFileStore {
1381
2315
  const queryKeywords = keywords(query.text);
1382
2316
  return filtered.map((entry) => ({ entry, score: relevanceScore(queryKeywords, entry) })).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).slice(0, query.limit ?? 10).map(({ entry }) => entry);
1383
2317
  }
1384
- write(entry) {
2318
+ store(entry) {
1385
2319
  return this.mutate((entries) => {
1386
2320
  const index = entries.findIndex((e) => e.key === entry.key);
1387
2321
  const stamped = {
@@ -1412,11 +2346,40 @@ var RememberaMemoryProvider = class {
1412
2346
  this.env = config.env ?? process.env;
1413
2347
  this.log = log.child({ component: "remembera" });
1414
2348
  }
2349
+ get authenticationConfigured() {
2350
+ return this.apiKeyEnvVar === void 0 || Boolean(this.env[this.apiKeyEnvVar]);
2351
+ }
1415
2352
  headers() {
1416
2353
  const key = this.apiKeyEnvVar ? resolveApiKey({ name: "remembera", apiKeyEnvVar: this.apiKeyEnvVar }, this.env) : void 0;
1417
2354
  return key ? { authorization: `Bearer ${key}` } : {};
1418
2355
  }
1419
- async query(query) {
2356
+ /** Search delegates to the gateway's relevance query. */
2357
+ search(text, options = {}) {
2358
+ return defaultSearch(
2359
+ this,
2360
+ text,
2361
+ {
2362
+ ...options.scope !== void 0 ? { scope: options.scope } : {},
2363
+ ...options.limit !== void 0 ? { limit: options.limit } : {}
2364
+ }
2365
+ );
2366
+ }
2367
+ metadata() {
2368
+ return defaultMetadata(this.name, {
2369
+ capabilities: ["retrieve", "store", "search", "categories", "tags"],
2370
+ relevanceRanking: true,
2371
+ backend: { kind: "http", baseUrl: this.baseUrl, authenticated: this.authenticationConfigured }
2372
+ });
2373
+ }
2374
+ /** @deprecated v1 alias of {@link retrieve}. */
2375
+ query(query) {
2376
+ return this.retrieve(query);
2377
+ }
2378
+ /** @deprecated v1 alias of {@link store}. */
2379
+ write(entry) {
2380
+ return this.store(entry);
2381
+ }
2382
+ async retrieve(query) {
1420
2383
  const { status, body } = await this.request(`${this.baseUrl}/api/memory/query`, query);
1421
2384
  if (status !== 200) {
1422
2385
  throw new MemoryError(`Remembera query returned HTTP ${status}.`);
@@ -1427,7 +2390,7 @@ var RememberaMemoryProvider = class {
1427
2390
  }
1428
2391
  return entries.map((entry) => this.toEntry(entry)).filter((entry) => entry !== void 0);
1429
2392
  }
1430
- async write(entry) {
2393
+ async store(entry) {
1431
2394
  const { status } = await this.request(`${this.baseUrl}/api/memory`, entry);
1432
2395
  if (status !== 200 && status !== 201) {
1433
2396
  throw new MemoryError(`Remembera write returned HTTP ${status}.`);
@@ -1462,6 +2425,7 @@ var RememberaMemoryProvider = class {
1462
2425
  content: entry.content,
1463
2426
  scope: scope === "project" || scope === "role" || scope === "task" || scope === "knowledge" ? scope : "knowledge",
1464
2427
  ...Array.isArray(entry.tags) ? { tags: entry.tags.filter((t) => typeof t === "string") } : {},
2428
+ ...typeof entry.category === "string" ? { category: entry.category } : {},
1465
2429
  ...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
1466
2430
  ...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {}
1467
2431
  };
@@ -1492,102 +2456,387 @@ function createMemoryProvider(config) {
1492
2456
  }
1493
2457
 
1494
2458
  // src/memory/service.ts
2459
+ var DEFAULT_ENTRY_LIMIT = 8;
1495
2460
  var MAX_CONTEXT_CHARS = 4e3;
1496
2461
  var MemoryService = class {
1497
- constructor(provider, log = logger) {
2462
+ constructor(provider, log = logger, options = {}) {
1498
2463
  this.provider = provider;
1499
2464
  this.log = log.child({ component: "memory", provider: provider.name });
2465
+ this.required = options.required ?? false;
2466
+ this.maxContextChars = options.maxContextChars ?? MAX_CONTEXT_CHARS;
1500
2467
  }
1501
2468
  provider;
1502
2469
  log;
2470
+ required;
2471
+ maxContextChars;
1503
2472
  get providerRef() {
1504
2473
  return this.provider;
1505
2474
  }
1506
2475
  get providerName() {
1507
2476
  return this.provider.name;
1508
2477
  }
1509
- /** Relevant context for a task objective; empty string when nothing matches. */
1510
- async gatherContext(query) {
2478
+ get isRequired() {
2479
+ return this.required;
2480
+ }
2481
+ /** Structured, budgeted context for a task (§12 — avoid loading all memory). */
2482
+ async contextFor(query) {
2483
+ let entries;
1511
2484
  try {
1512
- const entries = await this.provider.query({
2485
+ entries = await this.provider.retrieve({
1513
2486
  text: query.objective,
1514
2487
  ...query.role ? { tags: [query.role] } : {},
1515
- limit: 8
2488
+ limit: query.limit ?? DEFAULT_ENTRY_LIMIT
1516
2489
  });
1517
- if (entries.length === 0) return "";
1518
- const byScope = /* @__PURE__ */ new Map();
1519
- for (const entry of entries) {
1520
- byScope.set(entry.scope, [...byScope.get(entry.scope) ?? [], entry]);
1521
- }
1522
- const parts = [];
1523
- for (const scope of ["project", "task", "role", "knowledge"]) {
1524
- const scopeEntries = byScope.get(scope) ?? [];
1525
- if (scopeEntries.length === 0) continue;
1526
- const lines = scopeEntries.map((entry) => `- ${entry.content}`).join("\n");
1527
- parts.push(`### Relevant memory (${scope})
1528
- ${lines}`);
1529
- }
1530
- const context = parts.join("\n\n");
1531
- return context.length > MAX_CONTEXT_CHARS ? `${context.slice(0, MAX_CONTEXT_CHARS)}\u2026` : context;
1532
2490
  } catch (error) {
1533
- this.log.warn(
1534
- `Memory query failed (continuing without memory): ${error instanceof Error ? error.message : String(error)}`
1535
- );
1536
- return "";
2491
+ return this.handleFailure("retrieve", error, { text: "", entries: [], truncated: false });
1537
2492
  }
2493
+ return this.format(entries);
1538
2494
  }
1539
- /** Records a finished task's outcome — best-effort. */
2495
+ /** Prompt-ready context text (v1 convenience wrapper). */
2496
+ async gatherContext(query) {
2497
+ return (await this.contextFor(query)).text;
2498
+ }
2499
+ /** Records a finished task's outcome — task history plus "known problems" (§12). */
1540
2500
  async recordTaskOutcome(task) {
1541
2501
  const outcome = task.result?.summary ?? task.result?.error ?? "(no output)";
1542
2502
  try {
1543
- await this.provider.write({
2503
+ await this.provider.store({
1544
2504
  key: `task:${task.id}`,
1545
2505
  scope: "task",
2506
+ category: "tasks",
1546
2507
  content: `Objective: ${task.objective}
1547
2508
  Status: ${task.status}
1548
2509
  Outcome: ${outcome}`,
1549
2510
  tags: ["task", ...task.agent ? [task.agent] : [], ...task.role ? [task.role] : []],
1550
2511
  createdAt: task.createdAt
1551
2512
  });
1552
- const significant = (task.result?.reports ?? []).flatMap(
1553
- (report) => report.findings.filter((finding) => finding.severity === "high" || finding.severity === "critical").map((finding) => ({ report, finding }))
1554
- );
1555
- for (const { report, finding } of significant) {
1556
- await this.provider.write({
1557
- key: `finding:${report.id}:${finding.id}`,
1558
- scope: "project",
1559
- content: `[${finding.severity}] ${finding.title} \u2014 ${finding.detail}`,
1560
- tags: ["known-problem", report.agent]
1561
- });
1562
- }
1563
- } catch (error) {
1564
- this.log.warn(
1565
- `Memory write failed (continuing): ${error instanceof Error ? error.message : String(error)}`
1566
- );
2513
+ const significant = (task.result?.reports ?? []).flatMap(
2514
+ (report) => report.findings.filter((finding) => finding.severity === "high" || finding.severity === "critical").map((finding) => ({ report, finding }))
2515
+ );
2516
+ for (const { report, finding } of significant) {
2517
+ await this.provider.store({
2518
+ key: `finding:${report.id}:${finding.id}`,
2519
+ scope: "project",
2520
+ category: "problems",
2521
+ content: `[${finding.severity}] ${finding.title} \u2014 ${finding.detail}`,
2522
+ tags: ["known-problem", report.agent]
2523
+ });
2524
+ }
2525
+ } catch (error) {
2526
+ this.handleFailure("store", error, void 0);
2527
+ }
2528
+ }
2529
+ format(entries) {
2530
+ if (entries.length === 0) return { text: "", entries, truncated: false };
2531
+ const byScope = /* @__PURE__ */ new Map();
2532
+ for (const entry of entries) {
2533
+ byScope.set(entry.scope, [...byScope.get(entry.scope) ?? [], entry]);
2534
+ }
2535
+ const parts = [];
2536
+ for (const scope of ["project", "task", "role", "knowledge"]) {
2537
+ const scopeEntries = byScope.get(scope) ?? [];
2538
+ if (scopeEntries.length === 0) continue;
2539
+ const lines = scopeEntries.map((entry) => {
2540
+ const category = entry.category ? `[${entry.category}] ` : "";
2541
+ return `- ${category}${entry.content}`;
2542
+ }).join("\n");
2543
+ parts.push(`### Relevant memory (${scope})
2544
+ ${lines}`);
2545
+ }
2546
+ let text = parts.join("\n\n");
2547
+ const truncated = text.length > this.maxContextChars;
2548
+ if (truncated) text = `${text.slice(0, this.maxContextChars - 1)}\u2026`;
2549
+ return { text, entries, truncated };
2550
+ }
2551
+ /**
2552
+ * Failure policy (§12): continue unless memory is explicitly required.
2553
+ */
2554
+ handleFailure(operation, error, fallback) {
2555
+ const message = error instanceof Error ? error.message : String(error);
2556
+ if (this.required) {
2557
+ throw new MemoryError(
2558
+ `Memory is marked required, but ${operation} failed: ${message}`,
2559
+ { cause: error }
2560
+ );
2561
+ }
2562
+ this.log.warn(`Memory ${operation} failed (continuing without memory): ${message}`);
2563
+ return fallback ?? { text: "", entries: [], truncated: false };
2564
+ }
2565
+ };
2566
+
2567
+ // src/tasks/engine-factory.ts
2568
+ function createTaskEngine(config, log = logger, directory) {
2569
+ const agents = buildAgentRegistry(config, log);
2570
+ const roles = buildRoleRegistry(config);
2571
+ return new TaskEngine({
2572
+ agents,
2573
+ roles,
2574
+ store: new FileTaskStore(directory),
2575
+ ...config.project.memory !== void 0 ? {
2576
+ memory: new MemoryService(createMemoryProvider(config.project.memory), log, {
2577
+ required: config.project.memory.required
2578
+ })
2579
+ } : {},
2580
+ audit: new FileAuditLog(),
2581
+ log
2582
+ });
2583
+ }
2584
+
2585
+ // src/pipelines/engine.ts
2586
+ import { randomBytes as randomBytes5 } from "crypto";
2587
+
2588
+ // src/reports/aggregate.ts
2589
+ import { randomBytes as randomBytes2 } from "crypto";
2590
+
2591
+ // src/reports/schema.ts
2592
+ import { z as z2 } from "zod";
2593
+ var evidenceSchema = z2.union([
2594
+ z2.string().transform((excerpt) => ({ kind: "observation", excerpt })),
2595
+ z2.object({
2596
+ kind: z2.enum(["file", "line", "url", "command", "observation", "artifact"]),
2597
+ ref: z2.string().optional(),
2598
+ line: z2.number().int().optional(),
2599
+ excerpt: z2.string().optional(),
2600
+ agent: z2.string().optional()
2601
+ })
2602
+ ]);
2603
+ var findingSchema = z2.object({
2604
+ id: z2.string().min(1),
2605
+ title: z2.string().min(1),
2606
+ detail: z2.string().default(""),
2607
+ description: z2.string().optional(),
2608
+ severity: z2.enum(["info", "low", "medium", "high", "critical"]).optional(),
2609
+ confidence: z2.enum(["low", "medium", "high"]).optional(),
2610
+ severityBySource: z2.record(z2.enum(["info", "low", "medium", "high", "critical"])).optional(),
2611
+ confidenceBySource: z2.record(z2.enum(["low", "medium", "high"])).optional(),
2612
+ recommendations: z2.array(z2.string()).optional(),
2613
+ evidence: z2.array(evidenceSchema).optional(),
2614
+ location: z2.string().optional(),
2615
+ tags: z2.array(z2.string()).optional(),
2616
+ source: z2.object({ agent: z2.string().optional(), reportId: z2.string().optional(), pipelineRunId: z2.string().optional() }).optional(),
2617
+ recommendation: z2.string().optional()
2618
+ });
2619
+ var artifactSchema = z2.object({
2620
+ kind: z2.enum(["file", "patch", "log", "screenshot", "document", "data"]),
2621
+ ref: z2.string().min(1),
2622
+ description: z2.string().optional(),
2623
+ mediaType: z2.string().optional(),
2624
+ bytes: z2.number().int().optional()
2625
+ });
2626
+ var reportSchema = z2.object({
2627
+ id: z2.string().min(1),
2628
+ taskId: z2.string().min(1),
2629
+ agent: z2.string().min(1),
2630
+ agentId: z2.string().optional(),
2631
+ pipelineRunId: z2.string().optional(),
2632
+ model: z2.string().optional(),
2633
+ summary: z2.string(),
2634
+ findings: z2.array(findingSchema),
2635
+ recommendations: z2.array(z2.string()),
2636
+ confidence: z2.enum(["low", "medium", "high"]).optional(),
2637
+ evidence: z2.array(evidenceSchema).optional(),
2638
+ artifacts: z2.array(artifactSchema).optional(),
2639
+ createdAt: z2.string().min(1),
2640
+ metadata: z2.record(z2.unknown()).optional()
2641
+ });
2642
+ function validateReport(value) {
2643
+ const parsed = reportSchema.safeParse(value);
2644
+ if (!parsed.success) {
2645
+ const issues = parsed.error.issues.map((issue) => ({
2646
+ path: issue.path.join(".") || "(root)",
2647
+ message: issue.message
2648
+ }));
2649
+ return {
2650
+ valid: false,
2651
+ issues,
2652
+ reason: `report failed schema validation (${issues.length} issue(s))`
2653
+ };
2654
+ }
2655
+ const report = parsed.data;
2656
+ report.findings = report.findings.map((finding) => ({
2657
+ ...finding,
2658
+ description: finding.description ?? finding.detail,
2659
+ ...finding.source === void 0 ? { source: { agent: report.agent, reportId: report.id, ...report.pipelineRunId !== void 0 ? { pipelineRunId: report.pipelineRunId } : {} } } : {}
2660
+ }));
2661
+ return { valid: true, report };
2662
+ }
2663
+ function normalizeEvidence(input) {
2664
+ if (input === void 0) return void 0;
2665
+ return input.map(
2666
+ (item) => typeof item === "string" ? { kind: "observation", excerpt: item } : item
2667
+ );
2668
+ }
2669
+ function normalizeFinding(finding) {
2670
+ return {
2671
+ ...finding,
2672
+ description: finding.description ?? finding.detail,
2673
+ evidence: normalizeEvidence(finding.evidence)
2674
+ };
2675
+ }
2676
+
2677
+ // src/reports/aggregate.ts
2678
+ var SEVERITY_RANK = {
2679
+ info: 0,
2680
+ low: 1,
2681
+ medium: 2,
2682
+ high: 3,
2683
+ critical: 4
2684
+ };
2685
+ function aggregateReports(reports, options = {}) {
2686
+ const threshold = options.similarityThreshold ?? 0.7;
2687
+ const findingGroups = [];
2688
+ const recommendationGroups = [];
2689
+ for (const report of reports) {
2690
+ for (const finding of report.findings) {
2691
+ mergeFinding(findingGroups, finding, report, threshold);
2692
+ }
2693
+ for (const text of report.recommendations) {
2694
+ mergeRecommendation(recommendationGroups, text, report, threshold);
2695
+ }
2696
+ }
2697
+ const sources = [...new Set(reports.map((r) => r.agent))].sort();
2698
+ const totalRawFindings = reports.reduce((sum, r) => sum + r.findings.length, 0);
2699
+ const conflicts = detectConflicts(findingGroups);
2700
+ const agreements = findingGroups.filter(
2701
+ (group) => group.sources.length > 1 && group.severity !== void 0 && new Set(Object.values(group.severityBySource ?? {})).size === 1
2702
+ );
2703
+ const disputed = conflicts.length;
2704
+ 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).`;
2705
+ return {
2706
+ id: `combined-${Date.now().toString(36)}-${randomBytes2(3).toString("hex")}`,
2707
+ title: options.objective !== void 0 ? truncate(options.objective, 120) : "Combined report",
2708
+ ...options.objective !== void 0 ? { objective: options.objective } : {},
2709
+ ...options.pipelineRunId !== void 0 ? { pipelineRunId: options.pipelineRunId } : {},
2710
+ taskIds: options.taskIds ?? [],
2711
+ summary,
2712
+ findings: findingGroups.sort((a, b) => rank(b) - rank(a)),
2713
+ agreements,
2714
+ conflicts,
2715
+ recommendations: recommendationGroups,
2716
+ sources,
2717
+ reportIds: reports.map((r) => r.id),
2718
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2719
+ metadata: {
2720
+ agents: sources.length,
2721
+ reports: reports.length,
2722
+ rawFindings: totalRawFindings,
2723
+ findingGroups: findingGroups.length,
2724
+ agreements: agreements.length,
2725
+ conflicts: conflicts.length
2726
+ }
2727
+ };
2728
+ }
2729
+ function rankConfidence(confidence) {
2730
+ return confidence ? { low: 1, medium: 2, high: 3 }[confidence] : 0;
2731
+ }
2732
+ function detectConflicts(groups) {
2733
+ const conflicts = [];
2734
+ for (const group of groups) {
2735
+ if (group.sources.length < 2) continue;
2736
+ const severities = new Set(Object.values(group.severityBySource ?? {}));
2737
+ if (severities.size > 1) {
2738
+ conflicts.push({
2739
+ type: "severity",
2740
+ findingTitle: group.title,
2741
+ positions: group.sources.map((source) => position(source, group)),
2742
+ note: `Agents disagree on severity (${[...severities].sort((a, b) => SEVERITY_RANK[a] - SEVERITY_RANK[b]).join(", ")}); the highest severity is shown, every position is preserved.`
2743
+ });
2744
+ }
2745
+ const recommendations = (group.recommendations ?? []).filter((r) => r.length > 0);
2746
+ if (recommendations.length > 1) {
2747
+ conflicts.push({
2748
+ type: "recommendation",
2749
+ findingTitle: group.title,
2750
+ positions: group.sources.map((source) => position(source, group)),
2751
+ note: `Agents proposed ${recommendations.length} different fixes; none was discarded.`
2752
+ });
1567
2753
  }
1568
2754
  }
1569
- };
1570
-
1571
- // src/tasks/engine-factory.ts
1572
- function createTaskEngine(config, log = logger, directory) {
1573
- const agents = buildAgentRegistry(config, log);
1574
- const roles = buildRoleRegistry(config);
1575
- return new TaskEngine({
1576
- agents,
1577
- roles,
1578
- store: new FileTaskStore(directory),
1579
- ...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
1580
- audit: new FileAuditLog(),
1581
- log
2755
+ return conflicts;
2756
+ }
2757
+ function position(source, group) {
2758
+ return {
2759
+ agent: source.agent,
2760
+ reportId: source.reportId,
2761
+ ...group.severityBySource?.[source.agent] !== void 0 ? { severity: group.severityBySource[source.agent] } : {},
2762
+ ...group.confidenceBySource?.[source.agent] !== void 0 ? { confidence: group.confidenceBySource[source.agent] } : {},
2763
+ ...group.recommendations?.[0] !== void 0 ? { recommendation: group.recommendations[0] } : {}
2764
+ };
2765
+ }
2766
+ function rank(finding) {
2767
+ return finding.severity ? SEVERITY_RANK[finding.severity] : -1;
2768
+ }
2769
+ function mergeFinding(groups, finding, report, threshold) {
2770
+ const source = { agent: report.agent, reportId: report.id };
2771
+ const group = groups.find((g) => similar(g.title, finding.title, threshold));
2772
+ if (group) {
2773
+ group.occurrences += 1;
2774
+ group.details.push(finding.detail);
2775
+ group.sources.push(source);
2776
+ group.evidence.push(...normalizeEvidence(finding.evidence) ?? []);
2777
+ if (finding.severity && (!group.severity || SEVERITY_RANK[finding.severity] > SEVERITY_RANK[group.severity])) {
2778
+ group.severity = finding.severity;
2779
+ }
2780
+ if (finding.confidence && rankConfidence(finding.confidence) > rankConfidence(group.confidence)) {
2781
+ group.confidence = finding.confidence;
2782
+ }
2783
+ if (finding.severity) {
2784
+ group.severityBySource = { ...group.severityBySource, [report.agent]: finding.severity };
2785
+ }
2786
+ if (finding.confidence) {
2787
+ group.confidenceBySource = { ...group.confidenceBySource, [report.agent]: finding.confidence };
2788
+ }
2789
+ if (finding.recommendation && !group.recommendations?.includes(finding.recommendation)) {
2790
+ group.recommendations = [...group.recommendations ?? [], finding.recommendation];
2791
+ }
2792
+ return;
2793
+ }
2794
+ groups.push({
2795
+ title: finding.title,
2796
+ details: [finding.detail],
2797
+ sources: [source],
2798
+ occurrences: 1,
2799
+ ...finding.severity ? { severity: finding.severity } : {},
2800
+ ...finding.confidence ? { confidence: finding.confidence } : {},
2801
+ ...finding.severity ? { severityBySource: { [report.agent]: finding.severity } } : {},
2802
+ ...finding.confidence ? { confidenceBySource: { [report.agent]: finding.confidence } } : {},
2803
+ ...finding.recommendation ? { recommendations: [finding.recommendation] } : {},
2804
+ evidence: [...normalizeEvidence(finding.evidence) ?? []]
1582
2805
  });
1583
2806
  }
1584
-
1585
- // src/pipelines/engine.ts
1586
- import { randomBytes as randomBytes4 } from "crypto";
2807
+ function mergeRecommendation(groups, text, report, threshold) {
2808
+ const source = { agent: report.agent, reportId: report.id };
2809
+ const group = groups.find((g) => similar(g.text, text, threshold));
2810
+ if (group) {
2811
+ group.occurrences += 1;
2812
+ group.sources.push(source);
2813
+ return;
2814
+ }
2815
+ groups.push({ text, sources: [source], occurrences: 1 });
2816
+ }
2817
+ function similar(a, b, threshold) {
2818
+ const left = tokens(a);
2819
+ const right = tokens(b);
2820
+ if (left.size === 0 || right.size === 0) return false;
2821
+ let intersection = 0;
2822
+ for (const token of left) {
2823
+ if (right.has(token)) intersection += 1;
2824
+ }
2825
+ const union = left.size + right.size - intersection;
2826
+ return intersection / union >= threshold;
2827
+ }
2828
+ function tokens(text) {
2829
+ return new Set(
2830
+ text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3)
2831
+ );
2832
+ }
2833
+ function truncate(text, max) {
2834
+ return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
2835
+ }
1587
2836
 
1588
2837
  // src/planner/heuristic.ts
1589
- import { randomBytes as randomBytes2 } from "crypto";
1590
- var SEVERITY_RANK = {
2838
+ import { randomBytes as randomBytes3 } from "crypto";
2839
+ var SEVERITY_RANK2 = {
1591
2840
  info: 0,
1592
2841
  low: 1,
1593
2842
  medium: 2,
@@ -1598,19 +2847,32 @@ var HeuristicPlanner = class {
1598
2847
  name = "heuristic";
1599
2848
  async plan(request) {
1600
2849
  const steps = [];
1601
- const findings = request.reports.flatMap(
1602
- (report) => report.findings.map((finding) => ({ finding, agent: report.agent }))
1603
- );
1604
2850
  const recommendations = dedupe(request.reports.flatMap((r) => r.recommendations));
1605
- for (const [index, text] of recommendations.entries()) {
2851
+ for (const text of recommendations) {
1606
2852
  steps.push({
1607
2853
  id: `step-${steps.length + 1}`,
1608
- title: truncate(text, 120),
2854
+ title: truncate2(text, 120),
1609
2855
  order: steps.length + 1,
1610
- ...index > 0 ? { dependsOn: [`step-${steps.length}`] } : {}
2856
+ ...steps.length > 0 ? { dependsOn: [`step-${steps.length}`] } : {}
1611
2857
  });
1612
2858
  }
1613
- const significant = findings.filter(({ finding }) => finding.severity ? SEVERITY_RANK[finding.severity] >= SEVERITY_RANK.high : false).sort((a, b) => SEVERITY_RANK[b.finding.severity ?? "info"] - SEVERITY_RANK[a.finding.severity ?? "info"]);
2859
+ const contributed = request.aggregated ? request.aggregated.findings.map((finding) => ({
2860
+ finding: {
2861
+ id: `agg-${slug(finding.title)}`,
2862
+ title: finding.title,
2863
+ detail: finding.details[0] ?? finding.title,
2864
+ ...finding.severity ? { severity: finding.severity } : {},
2865
+ ...finding.recommendations && finding.recommendations.length > 0 ? { recommendation: finding.recommendations[0] } : {}
2866
+ },
2867
+ agent: finding.sources.map((s) => s.agent).join("+")
2868
+ })) : request.reports.flatMap(
2869
+ (report) => report.findings.map((finding) => ({ finding, agent: report.agent }))
2870
+ );
2871
+ const significant = contributed.filter(
2872
+ ({ finding }) => finding.severity ? SEVERITY_RANK2[finding.severity] >= SEVERITY_RANK2.high : false
2873
+ ).sort(
2874
+ (a, b) => SEVERITY_RANK2[b.finding.severity ?? "info"] - SEVERITY_RANK2[a.finding.severity ?? "info"]
2875
+ );
1614
2876
  for (const { finding } of significant) {
1615
2877
  steps.push(remediationStep(finding, steps.length + 1));
1616
2878
  }
@@ -1623,12 +2885,20 @@ var HeuristicPlanner = class {
1623
2885
  });
1624
2886
  }
1625
2887
  const risks = significant.filter(({ finding }) => finding.severity === "critical").map(({ finding }) => `Critical finding unaddressed until its step completes: ${finding.title}`);
2888
+ const conflicts = request.aggregated?.conflicts ?? [];
2889
+ if (conflicts.length > 0) {
2890
+ risks.push(
2891
+ `${conflicts.length} unresolved agent disagreement(s): ${conflicts.map((c) => c.findingTitle).join("; ")}`
2892
+ );
2893
+ }
1626
2894
  return {
1627
- id: `plan-${Date.now().toString(36)}-${randomBytes2(3).toString("hex")}`,
2895
+ id: `plan-${Date.now().toString(36)}-${randomBytes3(3).toString("hex")}`,
1628
2896
  objective: request.objective,
2897
+ goal: request.aggregated?.title ?? truncate2(request.objective, 120),
1629
2898
  summary: `Derived from ${request.reports.length} report(s): ${recommendations.length} recommendation step(s), ${significant.length} remediation step(s).`,
1630
2899
  steps,
1631
2900
  ...risks.length > 0 ? { risks } : {},
2901
+ ...request.constraints !== void 0 && request.constraints.length > 0 ? { constraints: request.constraints } : {},
1632
2902
  generatedBy: "heuristic",
1633
2903
  ...request.taskId !== void 0 ? { taskId: request.taskId } : {},
1634
2904
  ...request.pipelineRunId !== void 0 ? { pipelineRunId: request.pipelineRunId } : {},
@@ -1639,28 +2909,80 @@ var HeuristicPlanner = class {
1639
2909
  function remediationStep(finding, order) {
1640
2910
  return {
1641
2911
  id: `step-${order}`,
1642
- title: `Fix: ${truncate(finding.title, 100)}`,
2912
+ title: `Fix: ${truncate2(finding.title, 100)}`,
1643
2913
  description: finding.detail,
1644
2914
  order,
1645
2915
  dependsOn: order > 1 ? [`step-${order - 1}`] : [],
1646
- sourceFindings: [finding.id]
2916
+ sourceFindings: [finding.id],
2917
+ ...finding.recommendation ? { recommendation: finding.recommendation } : {}
1647
2918
  };
1648
2919
  }
1649
2920
  function dedupe(texts) {
1650
2921
  return [...new Set(texts.map((t) => t.trim()).filter((t) => t.length > 0))];
1651
2922
  }
1652
- function truncate(text, max) {
2923
+ function slug(text) {
2924
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
2925
+ }
2926
+ function truncate2(text, max) {
1653
2927
  return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
1654
2928
  }
1655
2929
 
1656
2930
  // src/planner/model.ts
1657
- import { randomBytes as randomBytes3 } from "crypto";
1658
- import { z as z2 } from "zod";
2931
+ import { randomBytes as randomBytes4 } from "crypto";
2932
+
2933
+ // src/planner/schema.ts
2934
+ import { z as z3 } from "zod";
2935
+ var planStepSchema = z3.object({
2936
+ id: z3.string().min(1).optional(),
2937
+ title: z3.string().min(1),
2938
+ description: z3.string().optional(),
2939
+ order: z3.number().int().optional(),
2940
+ targets: z3.array(z3.string()).optional(),
2941
+ acceptance_criteria: z3.array(z3.string()).optional(),
2942
+ depends_on: z3.array(z3.string()).optional(),
2943
+ source_findings: z3.array(z3.string()).optional()
2944
+ });
2945
+ var planJsonSchema = z3.object({
2946
+ goal: z3.string().min(1),
2947
+ summary: z3.string().min(1),
2948
+ steps: z3.array(planStepSchema).min(1),
2949
+ risks: z3.array(z3.string()).optional(),
2950
+ notes: z3.array(z3.string()).optional()
2951
+ });
2952
+ function validatePlan(plan) {
2953
+ const issues = [];
2954
+ const stepIds = /* @__PURE__ */ new Set();
2955
+ for (const [index, step] of plan.steps.entries()) {
2956
+ if (stepIds.has(step.id)) {
2957
+ issues.push({ path: `steps.${index}.id`, message: `duplicate step id "${step.id}"` });
2958
+ }
2959
+ stepIds.add(step.id);
2960
+ if (!step.title || step.title.trim().length === 0) {
2961
+ issues.push({ path: `steps.${index}.title`, message: "title must not be empty" });
2962
+ }
2963
+ for (const dep of step.dependsOn ?? []) {
2964
+ if (!stepIds.has(dep) && !plan.steps.some((s) => s.id === dep)) {
2965
+ issues.push({ path: `steps.${index}.dependsOn`, message: `unknown step dependency "${dep}"` });
2966
+ }
2967
+ }
2968
+ }
2969
+ if (plan.steps.length === 0) {
2970
+ issues.push({ path: "steps", message: "a plan needs at least one step" });
2971
+ }
2972
+ if (!plan.summary || plan.summary.trim().length === 0) {
2973
+ issues.push({ path: "summary", message: "summary must not be empty" });
2974
+ }
2975
+ return issues.length > 0 ? { valid: false, issues, reason: `plan failed validation (${issues.length} issue(s))` } : { valid: true, plan };
2976
+ }
1659
2977
 
1660
2978
  // src/planner/context.ts
1661
2979
  function buildPlannerContext(request) {
1662
- const parts = [`# Objective
2980
+ const parts = [`# Task
1663
2981
  ${request.objective}`];
2982
+ if (request.projectContext) {
2983
+ parts.push(`# Project context
2984
+ ${request.projectContext}`);
2985
+ }
1664
2986
  if (request.role) {
1665
2987
  const roleLines = [`# Role
1666
2988
  ${request.role.name} (${request.role.id})`];
@@ -1680,7 +3002,7 @@ ${request.role.name} (${request.role.id})`];
1680
3002
  )
1681
3003
  );
1682
3004
  parts.push(
1683
- `# Findings (${request.reports.length} report(s) by ${agents})
3005
+ `# Agent reports (${request.reports.length} by ${agents})
1684
3006
  ` + (findingLines.length > 0 ? findingLines.join("\n") : "(no findings reported)")
1685
3007
  );
1686
3008
  const recommendationLines = request.reports.flatMap(
@@ -1690,9 +3012,20 @@ ${request.role.name} (${request.role.id})`];
1690
3012
  parts.push(`# Recommendations
1691
3013
  ${recommendationLines.join("\n")}`);
1692
3014
  }
1693
- const summaryLines = request.reports.map((report) => `- [${report.agent}] ${report.summary}`);
1694
- parts.push(`# Report summaries
1695
- ${summaryLines.join("\n")}`);
3015
+ }
3016
+ if (request.aggregated) {
3017
+ const aggregatedLines = [
3018
+ `- findings: ${request.aggregated.findings.length} (agreements: ${request.aggregated.agreements.length}, conflicts: ${request.aggregated.conflicts.length})`,
3019
+ ...request.aggregated.findings.map(
3020
+ (finding) => `- [${finding.severity ?? "info"}] ${finding.title} (x${finding.occurrences})`
3021
+ ),
3022
+ // Conflicts stay visible to the planner — it must plan around disagreement.
3023
+ ...request.aggregated.conflicts.map(
3024
+ (conflict) => `- CONFLICT (${conflict.type}) on "${conflict.findingTitle}": ${conflict.note}`
3025
+ )
3026
+ ];
3027
+ parts.push(`# Aggregated findings
3028
+ ${aggregatedLines.join("\n")}`);
1696
3029
  }
1697
3030
  if (request.memory && request.memory.length > 0) {
1698
3031
  parts.push(
@@ -1700,7 +3033,11 @@ ${summaryLines.join("\n")}`);
1700
3033
  ` + request.memory.map((entry) => `- [${entry.scope}] ${entry.content}`).join("\n")
1701
3034
  );
1702
3035
  }
1703
- if (request.context && request.context.length > 0) {
3036
+ if (request.constraints && request.constraints.length > 0) {
3037
+ parts.push(`# Constraints
3038
+ ${request.constraints.map((c) => `- ${c}`).join("\n")}`);
3039
+ }
3040
+ if (request.context) {
1704
3041
  parts.push(`# Research context
1705
3042
  ${request.context}`);
1706
3043
  }
@@ -1708,22 +3045,6 @@ ${request.context}`);
1708
3045
  }
1709
3046
 
1710
3047
  // src/planner/model.ts
1711
- var planStepJsonSchema = z2.object({
1712
- id: z2.string().optional(),
1713
- title: z2.string().min(1),
1714
- description: z2.string().optional(),
1715
- order: z2.number().int().optional(),
1716
- targets: z2.array(z2.string()).optional(),
1717
- acceptance_criteria: z2.array(z2.string()).optional(),
1718
- depends_on: z2.array(z2.string()).optional(),
1719
- source_findings: z2.array(z2.string()).optional()
1720
- }).strict();
1721
- var planJsonSchema = z2.object({
1722
- summary: z2.string().min(1),
1723
- steps: z2.array(planStepJsonSchema).min(1),
1724
- risks: z2.array(z2.string()).optional(),
1725
- notes: z2.array(z2.string()).optional()
1726
- }).strict();
1727
3048
  var ModelPlanner = class {
1728
3049
  constructor(options) {
1729
3050
  this.options = options;
@@ -1737,7 +3058,7 @@ var ModelPlanner = class {
1737
3058
  const messages = [
1738
3059
  {
1739
3060
  role: "system",
1740
- content: 'You are the OmniNode planner. Given the objective and the combined research context, produce a final analysis and a prioritized, actionable implementation plan. Respond with JSON ONLY (no prose, no code fences) matching exactly this shape: {"summary": string, "steps": [{"id": string, "title": string, "description": string, "order": number, "targets": string[], "acceptance_criteria": string[], "source_findings": string[]}], "risks": string[], "notes": string[]}. ' + (this.options.instruction ?? "")
3061
+ content: 'You are the OmniNode planner. Given the objective and the combined research context, produce a final analysis and a prioritized, actionable implementation plan. Respond with JSON ONLY (no prose, no code fences) matching exactly this shape: {"goal": string, "summary": string, "steps": [{"id": string, "title": string, "description": string, "order": number, "targets": string[], "acceptance_criteria": string[], "source_findings": string[]}], "risks": string[], "notes": string[]}. ' + (this.options.instruction ?? "")
1741
3062
  },
1742
3063
  { role: "user", content: context }
1743
3064
  ];
@@ -1748,60 +3069,86 @@ var ModelPlanner = class {
1748
3069
  return this.degrade(request, `planner model call failed: ${describe(error)}`);
1749
3070
  }
1750
3071
  const parsed = parsePlanJson(responseText);
1751
- if (!parsed) {
1752
- return this.degrade(request, "planner model output was not valid plan JSON");
3072
+ if (!parsed.ok) {
3073
+ return this.degrade(request, `planner model output rejected: ${parsed.reason}`, parsed.issues);
1753
3074
  }
1754
3075
  const plan = {
1755
- id: `plan-${Date.now().toString(36)}-${randomBytes3(3).toString("hex")}`,
3076
+ id: `plan-${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`,
1756
3077
  objective: request.objective,
3078
+ goal: parsed.goal,
1757
3079
  summary: parsed.summary,
1758
3080
  steps: parsed.steps,
1759
3081
  ...parsed.risks !== void 0 ? { risks: parsed.risks } : {},
1760
3082
  ...parsed.notes !== void 0 ? { notes: parsed.notes } : {},
3083
+ ...request.constraints !== void 0 && request.constraints.length > 0 ? { constraints: request.constraints } : {},
1761
3084
  generatedBy: `model:${this.options.model}`,
1762
3085
  model: this.options.model,
1763
3086
  ...request.taskId !== void 0 ? { taskId: request.taskId } : {},
1764
3087
  ...request.pipelineRunId !== void 0 ? { pipelineRunId: request.pipelineRunId } : {},
1765
3088
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1766
3089
  };
1767
- return plan;
3090
+ const validation = validatePlan(plan);
3091
+ if (!validation.valid) {
3092
+ return this.degrade(request, `generated plan rejected: ${validation.reason}`, validation.issues);
3093
+ }
3094
+ return validation.plan;
1768
3095
  }
1769
- async degrade(request, reason) {
3096
+ async degrade(request, reason, issues = []) {
1770
3097
  if (this.options.fallback) {
1771
3098
  this.log.warn(`${reason} \u2014 using the heuristic fallback planner.`);
1772
3099
  return this.options.fallback.plan(request);
1773
3100
  }
1774
- throw new PlannerError("PLANNER_FAILED", `Planner failed: ${reason}.`);
3101
+ throw new PlannerError("PLAN_INVALID", `Planner failed: ${reason}.`, {
3102
+ details: { reason, issues }
3103
+ });
1775
3104
  }
1776
3105
  };
1777
3106
  function parsePlanJson(text) {
1778
3107
  const unfenced = text.replace(/```(?:json)?/gi, "");
1779
3108
  const start = unfenced.indexOf("{");
1780
3109
  const end = unfenced.lastIndexOf("}");
1781
- if (start < 0 || end <= start) return void 0;
3110
+ if (start < 0 || end <= start) {
3111
+ return { ok: false, reason: "planner output contained no JSON object", issues: [] };
3112
+ }
3113
+ let json;
1782
3114
  try {
1783
- const json = JSON.parse(unfenced.slice(start, end + 1));
1784
- const parsed = planJsonSchema.safeParse(json);
1785
- if (!parsed.success) return void 0;
1786
- const steps = parsed.data.steps.map((step, index) => ({
1787
- id: step.id ?? `step-${index + 1}`,
1788
- title: step.title,
1789
- description: step.description,
1790
- order: step.order ?? index + 1,
1791
- targets: step.targets,
1792
- acceptanceCriteria: step.acceptance_criteria,
1793
- dependsOn: step.depends_on,
1794
- sourceFindings: step.source_findings
1795
- }));
3115
+ json = JSON.parse(unfenced.slice(start, end + 1));
3116
+ } catch (error) {
1796
3117
  return {
1797
- summary: parsed.data.summary,
1798
- steps: steps.sort((a, b) => a.order - b.order),
1799
- risks: parsed.data.risks,
1800
- notes: parsed.data.notes
3118
+ ok: false,
3119
+ reason: `planner output was not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
3120
+ issues: []
1801
3121
  };
1802
- } catch {
1803
- return void 0;
1804
3122
  }
3123
+ const parsed = planJsonSchema.safeParse(json);
3124
+ if (!parsed.success) {
3125
+ return {
3126
+ ok: false,
3127
+ reason: "planner output did not match the plan schema",
3128
+ issues: parsed.error.issues.map((issue) => ({
3129
+ path: issue.path.join(".") || "(root)",
3130
+ message: issue.message
3131
+ }))
3132
+ };
3133
+ }
3134
+ const steps = parsed.data.steps.map((step, index) => ({
3135
+ id: step.id ?? `step-${index + 1}`,
3136
+ title: step.title,
3137
+ description: step.description,
3138
+ order: step.order ?? index + 1,
3139
+ targets: step.targets,
3140
+ acceptanceCriteria: step.acceptance_criteria,
3141
+ dependsOn: step.depends_on,
3142
+ sourceFindings: step.source_findings
3143
+ })).sort((a, b) => a.order - b.order);
3144
+ return {
3145
+ ok: true,
3146
+ goal: parsed.data.goal,
3147
+ summary: parsed.data.summary,
3148
+ steps,
3149
+ risks: parsed.data.risks,
3150
+ notes: parsed.data.notes
3151
+ };
1805
3152
  }
1806
3153
  function describe(error) {
1807
3154
  return error instanceof Error ? error.message : String(error);
@@ -1815,9 +3162,23 @@ var PipelineEngine = class {
1815
3162
  }
1816
3163
  options;
1817
3164
  log;
3165
+ /** Runs executing in THIS process — cancellation can terminate them directly. */
3166
+ activeRuns = /* @__PURE__ */ new Map();
1818
3167
  get store() {
1819
3168
  return this.options.store;
1820
3169
  }
3170
+ /**
3171
+ * Saves the run while preserving fields that may be set by another process
3172
+ * (e.g. a cancellation request) — an in-memory snapshot must never clobber
3173
+ * externally requested cancellation (§9.2).
3174
+ */
3175
+ async persist(run) {
3176
+ const current = await this.options.store.get(run.id);
3177
+ await this.options.store.save({
3178
+ ...run,
3179
+ ...current?.cancellationRequested === true ? { cancellationRequested: true, cancellationRequestedAt: current.cancellationRequestedAt } : {}
3180
+ });
3181
+ }
1821
3182
  /** Structural + semantic validation (§27 Phase 5 — pipeline parser). */
1822
3183
  validate(def) {
1823
3184
  const ids = /* @__PURE__ */ new Set();
@@ -1917,18 +3278,44 @@ var PipelineEngine = class {
1917
3278
  );
1918
3279
  }
1919
3280
  }
3281
+ if (step.model) {
3282
+ const separator = step.model.indexOf(":");
3283
+ if (separator <= 0 || separator === step.model.length - 1) {
3284
+ throw new PipelineError(
3285
+ "PIPELINE_INVALID",
3286
+ `Step "${step.id}" has an invalid model reference "${step.model}" \u2014 use "provider:model-id".`
3287
+ );
3288
+ }
3289
+ const providerName = step.model.slice(0, separator);
3290
+ if (!this.options.providers.some((p) => p.name === providerName)) {
3291
+ throw new PipelineError(
3292
+ "PIPELINE_INVALID",
3293
+ `Step "${step.id}" uses model "${step.model}" but provider "${providerName}" is not configured.`
3294
+ );
3295
+ }
3296
+ }
1920
3297
  }
1921
3298
  const now = (/* @__PURE__ */ new Date()).toISOString();
1922
3299
  const run = {
1923
- id: `run-${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`,
3300
+ id: `run-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`,
1924
3301
  pipelineId: def.id,
3302
+ attempt: 1,
1925
3303
  objective,
1926
3304
  status: "running",
1927
3305
  startedAt: now,
1928
3306
  stepRuns: []
1929
3307
  };
1930
3308
  await this.options.store.save(run);
1931
- this.log.info(`Running pipeline ${def.id} as ${run.id}.`);
3309
+ const liveTaskIds = /* @__PURE__ */ new Set();
3310
+ this.activeRuns.set(run.id, {
3311
+ taskIds: liveTaskIds,
3312
+ cancelTasks: async () => {
3313
+ for (const taskId of liveTaskIds) {
3314
+ await this.options.tasks.cancel(taskId).catch(() => void 0);
3315
+ }
3316
+ }
3317
+ });
3318
+ this.log.info(`Running pipeline ${def.id} as ${run.id} (attempt ${run.attempt}).`);
1932
3319
  await this.options.audit?.record({
1933
3320
  at: (/* @__PURE__ */ new Date()).toISOString(),
1934
3321
  action: "pipeline.run.started",
@@ -1940,7 +3327,22 @@ var PipelineEngine = class {
1940
3327
  const pending = new Set(def.steps.map((s) => s.id));
1941
3328
  const contextParts = [];
1942
3329
  const allReports = [];
3330
+ let cancelled = false;
1943
3331
  while (pending.size > 0) {
3332
+ const persisted = await this.options.store.get(run.id);
3333
+ if (persisted?.cancellationRequested === true) {
3334
+ cancelled = true;
3335
+ run.cancellationRequested = true;
3336
+ run.cancellationRequestedAt = persisted.cancellationRequestedAt;
3337
+ await this.activeRuns.get(run.id)?.cancelTasks();
3338
+ for (const step of def.steps.filter((s) => pending.has(s.id))) {
3339
+ const stepRun = { stepId: step.id, status: "cancelled" };
3340
+ run.stepRuns.push(stepRun);
3341
+ options.onStep?.(stepRun);
3342
+ }
3343
+ pending.clear();
3344
+ break;
3345
+ }
1944
3346
  const ready = def.steps.filter(
1945
3347
  (step) => pending.has(step.id) && (deps.get(step.id) ?? []).every((d) => outcomes.has(d))
1946
3348
  );
@@ -1957,7 +3359,8 @@ var PipelineEngine = class {
1957
3359
  ...role ? { role } : {},
1958
3360
  combinedContext: contextParts.join("\n\n"),
1959
3361
  reports: [...allReports],
1960
- pipelineRunId: run.id
3362
+ pipelineRunId: run.id,
3363
+ runId: run.id
1961
3364
  };
1962
3365
  return this.executeStep(step, ctx);
1963
3366
  })
@@ -1974,7 +3377,9 @@ ${contribution}`);
1974
3377
  }
1975
3378
  const stepRun = {
1976
3379
  stepId: step.id,
1977
- status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : "completed",
3380
+ attempt: outcome.attempt ?? 1,
3381
+ ...outcome.executionIds && outcome.executionIds.length > 0 ? { executionIds: outcome.executionIds } : {},
3382
+ status: outcome.status === "skipped" ? "cancelled" : outcome.status === "failed" ? "failed" : outcome.status === "partial" ? "partially_completed" : "completed",
1978
3383
  ...outcome.taskIds.length > 0 ? { taskIds: outcome.taskIds } : {},
1979
3384
  ...outcome.planId !== void 0 ? { planId: outcome.planId } : {},
1980
3385
  ...outcome.error !== void 0 ? { error: outcome.error } : {}
@@ -1983,10 +3388,12 @@ ${contribution}`);
1983
3388
  options.onStep?.(stepRun);
1984
3389
  });
1985
3390
  run.reports = [...allReports];
1986
- await this.options.store.save(run);
3391
+ await this.persist(run);
1987
3392
  }
1988
- const failed = [...outcomes.values()].some((o) => o.status === "failed");
1989
- const finalStatus = failed ? "failed" : "completed";
3393
+ const allOutcomes = [...outcomes.values()];
3394
+ const failed = !cancelled && allOutcomes.some((o) => o.status === "failed");
3395
+ const partial = !cancelled && !failed && allOutcomes.some((o) => o.status === "partial");
3396
+ const finalStatus = cancelled ? "cancelled" : failed ? "failed" : partial ? "partial" : "completed";
1990
3397
  const planId = [...outcomes.values()].find((o) => o.planId !== void 0)?.planId;
1991
3398
  let resultSummary;
1992
3399
  for (let i = def.steps.length - 1; i >= 0; i -= 1) {
@@ -2014,16 +3421,19 @@ ${contribution}`);
2014
3421
  run.reports = collected;
2015
3422
  }
2016
3423
  }
3424
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
3425
+ this.activeRuns.delete(run.id);
2017
3426
  const finished = {
2018
3427
  ...run,
2019
3428
  status: finalStatus,
2020
- finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
3429
+ finishedAt,
3430
+ attempts: [...run.attempts ?? [], { attempt: run.attempt, startedAt: run.startedAt ?? finishedAt, finishedAt, status: finalStatus }],
2021
3431
  ...run.reports !== void 0 && run.reports.length > 0 ? { reports: run.reports } : {},
2022
3432
  ...combinedReportId !== void 0 ? { combinedReportId } : {},
2023
3433
  ...planId !== void 0 ? { planId } : {},
2024
3434
  ...resultSummary !== void 0 ? { resultSummary } : {}
2025
3435
  };
2026
- await this.options.store.save(finished);
3436
+ await this.persist(finished);
2027
3437
  this.log.info(`Pipeline ${def.id} finished: ${finalStatus}.`);
2028
3438
  await this.options.audit?.record({
2029
3439
  at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2033,6 +3443,23 @@ ${contribution}`);
2033
3443
  });
2034
3444
  return finished;
2035
3445
  }
3446
+ /** First-class cancellation (§9.2): requests cancellation and terminates
3447
+ * tasks that are running in this process immediately. */
3448
+ async cancel(runId) {
3449
+ const run = await this.options.store.get(runId);
3450
+ if (!run) {
3451
+ throw new PipelineError("PIPELINE_NOT_FOUND", `Pipeline run "${runId}" does not exist.`);
3452
+ }
3453
+ await this.options.store.save({
3454
+ ...run,
3455
+ cancellationRequested: true,
3456
+ cancellationRequestedAt: (/* @__PURE__ */ new Date()).toISOString()
3457
+ });
3458
+ const active = this.activeRuns.get(runId);
3459
+ if (active) {
3460
+ await active.cancelTasks();
3461
+ }
3462
+ }
2036
3463
  async get(runId) {
2037
3464
  return this.options.store.get(runId);
2038
3465
  }
@@ -2056,9 +3483,16 @@ ${contribution}`);
2056
3483
  this.log.warn(`Step "${step.id}" failed \u2014 retry ${attempt}/${retries}.`);
2057
3484
  }
2058
3485
  last = await this.executeStepOnce(step, ctx);
2059
- if (last.status !== "failed") return last;
3486
+ if (last.status !== "failed") return { ...last, attempt: attempt + 1 };
2060
3487
  }
2061
- return last;
3488
+ return { ...last, attempt: retries + 1 };
3489
+ }
3490
+ /** Registers tasks created for a step as live, so cancellation can terminate them (§9.2). */
3491
+ trackLiveTasks(runId, taskIds) {
3492
+ if (!runId) return;
3493
+ const active = this.activeRuns.get(runId);
3494
+ if (!active) return;
3495
+ for (const id of taskIds) active.taskIds.add(id);
2062
3496
  }
2063
3497
  async executeStepOnce(step, ctx) {
2064
3498
  switch (step.kind) {
@@ -2091,7 +3525,10 @@ ${contribution}`);
2091
3525
  })
2092
3526
  )
2093
3527
  );
3528
+ this.trackLiveTasks(ctx.runId, created.map((t) => t.id));
2094
3529
  const results = await Promise.all(created.map((task) => this.options.tasks.run(task.id)));
3530
+ const executionIds = results.map((r) => r.executions?.at(-1)?.executionId).filter((id) => id !== void 0);
3531
+ const completedTasks = results.filter((r) => r.status === "completed");
2095
3532
  const failedTasks = results.filter((r) => r.status !== "completed");
2096
3533
  const lines = results.map((result, index) => {
2097
3534
  const agentName = agentNames[index] ?? "unknown";
@@ -2101,16 +3538,25 @@ ${contribution}`);
2101
3538
  const reports = results.flatMap((r) => r.result?.reports ?? []);
2102
3539
  const summary = lines.join("\n");
2103
3540
  if (failedTasks.length > 0) {
2104
- const firstError = failedTasks[0]?.result?.error ?? "unknown error";
3541
+ const firstError = failedTasks[0]?.result?.error ?? failedTasks[0]?.lastError ?? "unknown outcome";
3542
+ const states = failedTasks.map((r) => r.status).join(", ");
2105
3543
  return {
2106
- status: "failed",
3544
+ // Some agents succeeded → the step produced usable but incomplete
3545
+ // intelligence: partial, not failed (roadmap §9.3).
3546
+ status: completedTasks.length > 0 ? "partial" : "failed",
2107
3547
  summary,
2108
3548
  taskIds: created.map((t) => t.id),
2109
- error: `${failedTasks.length}/${results.length} task(s) failed \u2014 ${firstError}`,
3549
+ error: `${failedTasks.length}/${results.length} task(s) incomplete (${states}) \u2014 ${firstError}`,
2110
3550
  reports
2111
3551
  };
2112
3552
  }
2113
- return { status: "completed", summary, taskIds: created.map((t) => t.id), reports };
3553
+ return {
3554
+ status: "completed",
3555
+ summary,
3556
+ taskIds: created.map((t) => t.id),
3557
+ ...executionIds.length > 0 ? { executionIds } : {},
3558
+ reports
3559
+ };
2114
3560
  }
2115
3561
  async runSingle(step, ctx, agentName) {
2116
3562
  const task = await this.options.tasks.create({
@@ -2119,10 +3565,13 @@ ${contribution}`);
2119
3565
  ...ctx.role ? { role: ctx.role.id } : {},
2120
3566
  ...ctx.combinedContext.length > 0 ? { context: { background: ctx.combinedContext } } : {}
2121
3567
  });
3568
+ this.trackLiveTasks(ctx.runId, [task.id]);
2122
3569
  const result = await this.options.tasks.run(task.id);
2123
3570
  if (result.status !== "completed") {
2124
3571
  return {
2125
- status: "failed",
3572
+ // Unknown/timed_out outcomes are not failures — the work may have
3573
+ // happened remotely; the step is incomplete, not broken.
3574
+ status: result.status === "unknown" || result.status === "timed_out" ? "partial" : "failed",
2126
3575
  summary: result.result?.summary ?? "",
2127
3576
  taskIds: [task.id],
2128
3577
  error: result.result?.error ?? `task ${task.id} did not complete`,
@@ -2137,11 +3586,14 @@ ${contribution}`);
2137
3586
  };
2138
3587
  }
2139
3588
  async runPlan(step, ctx) {
3589
+ const aggregated = ctx.reports.length > 0 ? aggregateReports(ctx.reports) : void 0;
2140
3590
  const request = {
2141
3591
  objective: ctx.objective,
2142
3592
  reports: ctx.reports,
3593
+ ...aggregated !== void 0 ? { aggregated } : {},
2143
3594
  ...ctx.combinedContext.length > 0 ? { context: ctx.combinedContext } : {},
2144
3595
  ...ctx.role ? { role: ctx.role } : {},
3596
+ ...step.constraints !== void 0 && step.constraints.length > 0 ? { constraints: step.constraints } : {},
2145
3597
  ...ctx.pipelineRunId !== void 0 ? { pipelineRunId: ctx.pipelineRunId } : {}
2146
3598
  };
2147
3599
  let planner;
@@ -2300,99 +3752,6 @@ function buildPlanner(plannerConfig, chat, log = logger) {
2300
3752
  });
2301
3753
  }
2302
3754
 
2303
- // src/reports/aggregate.ts
2304
- import { randomBytes as randomBytes5 } from "crypto";
2305
- var SEVERITY_RANK2 = {
2306
- info: 0,
2307
- low: 1,
2308
- medium: 2,
2309
- high: 3,
2310
- critical: 4
2311
- };
2312
- function aggregateReports(reports, options = {}) {
2313
- const threshold = options.similarityThreshold ?? 0.7;
2314
- const findingGroups = [];
2315
- const recommendationGroups = [];
2316
- for (const report of reports) {
2317
- for (const finding of report.findings) {
2318
- mergeFinding(findingGroups, finding, report, threshold);
2319
- }
2320
- for (const text of report.recommendations) {
2321
- mergeRecommendation(recommendationGroups, text, report, threshold);
2322
- }
2323
- }
2324
- const sources = [...new Set(reports.map((r) => r.agent))].sort();
2325
- const totalRawFindings = reports.reduce((sum, r) => sum + r.findings.length, 0);
2326
- const summary = `${reports.length} report(s) from ${sources.length > 0 ? sources.join(", ") : "no agents"}: ${findingGroups.length} unique finding group(s) (${totalRawFindings} raw), ${recommendationGroups.length} recommendation group(s).`;
2327
- return {
2328
- id: `combined-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`,
2329
- title: options.objective !== void 0 ? truncate2(options.objective, 120) : "Combined report",
2330
- ...options.objective !== void 0 ? { objective: options.objective } : {},
2331
- ...options.pipelineRunId !== void 0 ? { pipelineRunId: options.pipelineRunId } : {},
2332
- taskIds: options.taskIds ?? [],
2333
- summary,
2334
- findings: findingGroups.sort((a, b) => rank(b) - rank(a)),
2335
- recommendations: recommendationGroups,
2336
- sources,
2337
- reportIds: reports.map((r) => r.id),
2338
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2339
- };
2340
- }
2341
- function rank(finding) {
2342
- return finding.severity ? SEVERITY_RANK2[finding.severity] : -1;
2343
- }
2344
- function mergeFinding(groups, finding, report, threshold) {
2345
- const source = { agent: report.agent, reportId: report.id };
2346
- const group = groups.find((g) => similar(g.title, finding.title, threshold));
2347
- if (group) {
2348
- group.occurrences += 1;
2349
- group.details.push(finding.detail);
2350
- group.sources.push(source);
2351
- group.evidence.push(...finding.evidence ?? []);
2352
- if (finding.severity && (!group.severity || SEVERITY_RANK2[finding.severity] > SEVERITY_RANK2[group.severity])) {
2353
- group.severity = finding.severity;
2354
- }
2355
- return;
2356
- }
2357
- groups.push({
2358
- title: finding.title,
2359
- details: [finding.detail],
2360
- sources: [source],
2361
- occurrences: 1,
2362
- ...finding.severity ? { severity: finding.severity } : {},
2363
- evidence: [...finding.evidence ?? []]
2364
- });
2365
- }
2366
- function mergeRecommendation(groups, text, report, threshold) {
2367
- const source = { agent: report.agent, reportId: report.id };
2368
- const group = groups.find((g) => similar(g.text, text, threshold));
2369
- if (group) {
2370
- group.occurrences += 1;
2371
- group.sources.push(source);
2372
- return;
2373
- }
2374
- groups.push({ text, sources: [source], occurrences: 1 });
2375
- }
2376
- function similar(a, b, threshold) {
2377
- const left = tokens(a);
2378
- const right = tokens(b);
2379
- if (left.size === 0 || right.size === 0) return false;
2380
- let intersection = 0;
2381
- for (const token of left) {
2382
- if (right.has(token)) intersection += 1;
2383
- }
2384
- const union = left.size + right.size - intersection;
2385
- return intersection / union >= threshold;
2386
- }
2387
- function tokens(text) {
2388
- return new Set(
2389
- text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3)
2390
- );
2391
- }
2392
- function truncate2(text, max) {
2393
- return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
2394
- }
2395
-
2396
3755
  // src/reports/extract.ts
2397
3756
  var BULLET = /^(?:[-*•]|\d+[.)])\s+(.*)$/;
2398
3757
  function parseSections(raw) {
@@ -2442,6 +3801,7 @@ function extractReportFromText(raw, meta) {
2442
3801
  id: `${meta.taskId}-f-${index + 1}`,
2443
3802
  title: truncate3(text, 120),
2444
3803
  detail: text,
3804
+ description: text,
2445
3805
  severity: guessSeverity(text)
2446
3806
  }));
2447
3807
  return {
@@ -2452,7 +3812,8 @@ function extractReportFromText(raw, meta) {
2452
3812
  summary,
2453
3813
  findings,
2454
3814
  recommendations: recommendationTexts.map((t) => truncate3(t, 200)),
2455
- metadata: evidenceTexts.length > 0 ? { evidence: evidenceTexts } : void 0,
3815
+ ...evidenceTexts.length > 0 ? { evidence: evidenceTexts.map((excerpt) => ({ kind: "observation", excerpt })) } : {},
3816
+ metadata: evidenceTexts.length > 0 ? { evidenceCount: evidenceTexts.length } : void 0,
2456
3817
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
2457
3818
  };
2458
3819
  }
@@ -2468,27 +3829,46 @@ var ReportService = class {
2468
3829
  }
2469
3830
  store;
2470
3831
  log;
3832
+ /** Rejected reports from the last collection (§13 — malformed never enters aggregation). */
3833
+ lastDiagnostics = { collected: 0, accepted: 0, rejected: [] };
2471
3834
  get storeRef() {
2472
3835
  return this.store;
2473
3836
  }
2474
- /** Normalizes finished tasks into reports (§17 — normalizer). */
3837
+ /** Normalizes finished tasks into reports (§17 — normalizer), validating each
3838
+ * one so malformed reports never reach aggregation (§13). */
2475
3839
  async collectFromTasks(tasks) {
2476
- const reports = [];
3840
+ const candidates = [];
2477
3841
  for (const task of tasks) {
2478
3842
  const structured = task.result?.reports ?? [];
2479
3843
  if (structured.length > 0) {
2480
- reports.push(...structured);
3844
+ candidates.push(...structured);
2481
3845
  continue;
2482
3846
  }
2483
3847
  const text = task.result?.rawOutput ?? task.result?.summary ?? "";
2484
3848
  if (text.trim().length === 0) continue;
2485
- reports.push(
3849
+ candidates.push(
2486
3850
  extractReportFromText(text, {
2487
3851
  taskId: task.id,
2488
3852
  agent: task.agent ?? "unknown"
2489
3853
  })
2490
3854
  );
2491
3855
  }
3856
+ const reports = [];
3857
+ const rejected = [];
3858
+ for (const candidate of candidates) {
3859
+ const result = validateReport(candidate);
3860
+ if (result.valid) {
3861
+ reports.push(result.report);
3862
+ } else {
3863
+ rejected.push({
3864
+ key: candidate?.id ?? "(unknown)",
3865
+ reason: result.reason,
3866
+ issues: result.issues.map((issue) => `${issue.path}: ${issue.message}`)
3867
+ });
3868
+ this.log.warn(`Rejected malformed report: ${result.reason}`);
3869
+ }
3870
+ }
3871
+ this.lastDiagnostics = { collected: candidates.length, accepted: reports.length, rejected };
2492
3872
  return reports;
2493
3873
  }
2494
3874
  async saveReports(reports) {
@@ -2556,7 +3936,11 @@ function buildPipelineEngine(config, log = logger) {
2556
3936
  agents,
2557
3937
  roles,
2558
3938
  store: new FileTaskStore(),
2559
- ...config.project.memory !== void 0 ? { memory: new MemoryService(createMemoryProvider(config.project.memory), log) } : {},
3939
+ ...config.project.memory !== void 0 ? {
3940
+ memory: new MemoryService(createMemoryProvider(config.project.memory), log, {
3941
+ required: config.project.memory.required
3942
+ })
3943
+ } : {},
2560
3944
  audit: new FileAuditLog(),
2561
3945
  log
2562
3946
  });
@@ -2594,12 +3978,14 @@ var ProjectStores = class {
2594
3978
  };
2595
3979
 
2596
3980
  // src/version.ts
2597
- var OMNINODE_VERSION = "2.0.0-alpha.1";
3981
+ var OMNINODE_VERSION = "2.0.0-alpha.11";
2598
3982
  export {
3983
+ AgentAdapterRegistry,
2599
3984
  AgentError,
2600
3985
  AgentRegistry,
2601
3986
  ConfigError,
2602
3987
  ConsoleLogSink,
3988
+ DEFAULT_MAX_OUTPUT_BYTES,
2603
3989
  FileAuditLog,
2604
3990
  FilePipelineRunStore,
2605
3991
  FilePlanStore,
@@ -2609,6 +3995,8 @@ export {
2609
3995
  JsonFileStore,
2610
3996
  LocalMemoryProvider,
2611
3997
  Logger,
3998
+ MAX_CONTEXT_CHARS,
3999
+ MAX_MESSAGE_BYTES,
2612
4000
  MemoryError,
2613
4001
  MemoryService,
2614
4002
  ModelPlanner,
@@ -2618,16 +4006,22 @@ export {
2618
4006
  OmniHilbrasProvider,
2619
4007
  OmniNodeError,
2620
4008
  OpenAICompatibleProvider,
4009
+ PROTOCOL_V2,
2621
4010
  PipelineEngine,
2622
4011
  PipelineError,
2623
4012
  PlannerError,
2624
4013
  ProcessAgent,
4014
+ ProcessAgentAdapter,
2625
4015
  ProjectStores,
4016
+ ProtocolDecoder,
2626
4017
  ProtocolError,
4018
+ ProtocolSession,
2627
4019
  ProviderError,
2628
4020
  RememberaMemoryProvider,
2629
4021
  ReportService,
2630
4022
  RoleRegistry,
4023
+ SUPPORTED_PROTOCOL_VERSIONS,
4024
+ TERMINAL_TASK_STATUSES,
2631
4025
  TaskEngine,
2632
4026
  TaskError,
2633
4027
  agentConfigSchema,
@@ -2636,6 +4030,8 @@ export {
2636
4030
  authHeaders,
2637
4031
  buildAgentRegistry,
2638
4032
  buildChildEnv,
4033
+ buildHello,
4034
+ buildHelloAck,
2639
4035
  buildPipelineEngine,
2640
4036
  buildPlanner,
2641
4037
  buildPlannerContext,
@@ -2646,24 +4042,54 @@ export {
2646
4042
  createMemoryProvider,
2647
4043
  createProvider,
2648
4044
  createTaskEngine,
4045
+ decodeMessage,
4046
+ decodeStream,
4047
+ defaultMetadata,
2649
4048
  defaultProjectConfigYaml,
4049
+ defaultSearch,
4050
+ describeExit,
4051
+ detectConflicts,
4052
+ encodeMessage,
4053
+ envelope,
2650
4054
  expandEnvRefs,
4055
+ extractMessage,
2651
4056
  extractReportFromText,
2652
4057
  findConfigFile,
4058
+ isNetworkError,
2653
4059
  isOmniNodeError,
4060
+ isRetryable,
4061
+ isSupportedProtocol,
4062
+ isTimeoutError,
2654
4063
  keywords,
4064
+ killTree,
4065
+ kindFromStatus,
2655
4066
  loadConfig,
2656
4067
  logger,
4068
+ newMessageId,
4069
+ normalizeEvidence,
4070
+ normalizeFinding,
4071
+ parseHello,
2657
4072
  parsePlanJson,
4073
+ parseRetryAfter,
2658
4074
  pipelineConfigSchema,
2659
4075
  pipelineStepConfigSchema,
4076
+ planJsonSchema,
2660
4077
  plannerConfigSchema,
2661
4078
  projectConfigSchema,
2662
4079
  providerConfigSchema,
4080
+ providerHttpError,
4081
+ providerTransportError,
2663
4082
  registryKey,
2664
4083
  requestJson,
4084
+ requireCompletion,
2665
4085
  resolveApiKey,
4086
+ resolveEnvPolicy,
2666
4087
  roleConfigSchema,
2667
- similar
4088
+ sanitizeChunk,
4089
+ similar,
4090
+ validateEnvelope,
4091
+ validatePlan,
4092
+ validateReport,
4093
+ validateWorkingDirectory
2668
4094
  };
2669
4095
  //# sourceMappingURL=index.js.map