@cat-factory/orchestration 0.22.0 → 0.24.0

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.
@@ -0,0 +1,105 @@
1
+ import { DEFAULT_WORKSPACE_SETTINGS } from '@cat-factory/kernel';
2
+ /**
3
+ * Defensive upper bound (characters) on any single stored body — a prompt or one
4
+ * injected file. Real agent context sits far below this; the cap exists only so a
5
+ * pathological body can't blow past the store's per-value limit and make the whole
6
+ * snapshot fail to record. A truncated-but-recorded body beats a dropped one.
7
+ */
8
+ export const MAX_AGENT_CONTEXT_CHARS = 512 * 1024;
9
+ /**
10
+ * Aggregate ceiling (characters) across ALL bodies in one snapshot — both prompts plus
11
+ * every fragment and every injected file. The per-body {@link MAX_AGENT_CONTEXT_CHARS}
12
+ * cap bounds a single pathological value, but a dispatch that injects many large files
13
+ * could still assemble a multi-megabyte row. Recording is best-effort and swallowed at
14
+ * the call site, so an oversized row the store rejects would silently drop the WHOLE
15
+ * snapshot. This bounds the row instead. Bodies are filled in priority order (the
16
+ * prompts first), so the most useful context survives when the budget is reached.
17
+ */
18
+ export const MAX_AGENT_CONTEXT_TOTAL_CHARS = 4 * 1024 * 1024;
19
+ function clamp(text) {
20
+ if (text.length <= MAX_AGENT_CONTEXT_CHARS)
21
+ return text;
22
+ return `${text.slice(0, MAX_AGENT_CONTEXT_CHARS)}\n…[truncated ${text.length - MAX_AGENT_CONTEXT_CHARS} chars]`;
23
+ }
24
+ /**
25
+ * A shared character budget for one snapshot. Each call first applies the per-body cap,
26
+ * then trims against the remaining aggregate budget — so the bodies passed earlier (the
27
+ * prompts) are preserved and later ones (trailing injected files) are truncated once the
28
+ * row would grow too large. The trailing marker can push a single body marginally past
29
+ * the budget; the cap is defensive, not exact.
30
+ */
31
+ function makeBudget(total = MAX_AGENT_CONTEXT_TOTAL_CHARS) {
32
+ let remaining = total;
33
+ return (text) => {
34
+ const capped = clamp(text);
35
+ if (capped.length <= remaining) {
36
+ remaining -= capped.length;
37
+ return capped;
38
+ }
39
+ const slice = capped.slice(0, Math.max(0, remaining));
40
+ remaining = 0;
41
+ return `${slice}\n…[truncated: snapshot size budget reached]`;
42
+ };
43
+ }
44
+ /**
45
+ * The agent-context observability sink. The container-agent dispatch site (the
46
+ * {@link ContainerAgentExecutor}) calls {@link record} best-effort after dispatch with
47
+ * the assembled, redacted context (composed system + user prompts, folded-in fragment
48
+ * bodies, the files injected into the container). The sibling of
49
+ * {@link LlmObservabilityService}: that keeps what the model received per call, this
50
+ * keeps the complete context the agent was provided — including the `.cat-context`
51
+ * files the agent reads via tools, which never reach proxy telemetry.
52
+ *
53
+ * Storing is gated twice: the deployment-wide prompt-recording switch
54
+ * ({@link recordPrompts}) AND the per-workspace `storeAgentContext` setting must both be
55
+ * enabled. Wired only when a snapshot repository is present, so tests and unconfigured
56
+ * facades collect nothing.
57
+ */
58
+ export class AgentContextObservabilityService {
59
+ repository;
60
+ settings;
61
+ idGenerator;
62
+ clock;
63
+ recordPrompts;
64
+ constructor({ agentContextSnapshotRepository, workspaceSettingsRepository, idGenerator, clock, recordPrompts = true, }) {
65
+ this.repository = agentContextSnapshotRepository;
66
+ this.settings = workspaceSettingsRepository;
67
+ this.idGenerator = idGenerator;
68
+ this.clock = clock;
69
+ this.recordPrompts = recordPrompts;
70
+ }
71
+ /**
72
+ * Persist one dispatch's context, assigning its id + timestamp. Returns without
73
+ * storing when prompt recording is disabled deployment-wide or the workspace has
74
+ * turned `storeAgentContext` off. Bodies are clamped so an oversized prompt/file
75
+ * never drops the whole snapshot.
76
+ */
77
+ async record(input) {
78
+ if (!this.recordPrompts)
79
+ return;
80
+ if (!(await this.storeEnabled(input.workspaceId)))
81
+ return;
82
+ // One shared budget per snapshot, consumed prompts-first so the most useful context
83
+ // survives if a dispatch injects an unusual amount of file content.
84
+ const budget = makeBudget();
85
+ const snapshot = {
86
+ ...input,
87
+ id: this.idGenerator.next('ctx'),
88
+ createdAt: this.clock.now(),
89
+ systemPrompt: budget(input.systemPrompt),
90
+ userPrompt: budget(input.userPrompt),
91
+ fragments: input.fragments.map((f) => ({ id: f.id, body: budget(f.body) })),
92
+ contextFiles: input.contextFiles.map((f) => ({ ...f, content: budget(f.content) })),
93
+ };
94
+ await this.repository.record(snapshot);
95
+ }
96
+ /** Snapshots recorded for a run, newest first (for the observability drill-down). */
97
+ listByExecution(workspaceId, executionId) {
98
+ return this.repository.listByExecution(workspaceId, executionId);
99
+ }
100
+ async storeEnabled(workspaceId) {
101
+ const settings = (await this.settings.get(workspaceId)) ?? DEFAULT_WORKSPACE_SETTINGS;
102
+ return settings.storeAgentContext;
103
+ }
104
+ }
105
+ //# sourceMappingURL=AgentContextObservabilityService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentContextObservabilityService.js","sourceRoot":"","sources":["../../../src/modules/observability/AgentContextObservabilityService.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"WorkspaceSettingsService.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/WorkspaceSettingsService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,4BAA4B,EAC5B,mBAAmB,EACnB,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,qBAAqB,CAAA;AAG5B,MAAM,WAAW,oCAAoC;IACnD,2BAA2B,EAAE,2BAA2B,CAAA;IACxD,mBAAmB,EAAE,mBAAmB,CAAA;CACzC;AAED;;;;;;GAMG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,YAAY,IAAI,EAAE,oCAAoC,EAGrD;IAED,0FAA0F;IACpF,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAEzD;IAED,kEAAkE;IAC5D,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,iBAAiB,CAAC,CA+B5B;CACF"}
1
+ {"version":3,"file":"WorkspaceSettingsService.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/WorkspaceSettingsService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,4BAA4B,EAC5B,mBAAmB,EACnB,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,qBAAqB,CAAA;AAG5B,MAAM,WAAW,oCAAoC;IACnD,2BAA2B,EAAE,2BAA2B,CAAA;IACxD,mBAAmB,EAAE,mBAAmB,CAAA;CACzC;AAED;;;;;;GAMG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,YAAY,IAAI,EAAE,oCAAoC,EAGrD;IAED,0FAA0F;IACpF,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAEzD;IAED,kEAAkE;IAC5D,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,iBAAiB,CAAC,CAgC5B;CACF"}
@@ -26,6 +26,7 @@ export class WorkspaceSettingsService {
26
26
  taskLimitMode: patch.taskLimitMode ?? current.taskLimitMode,
27
27
  taskLimitShared: patch.taskLimitShared !== undefined ? patch.taskLimitShared : current.taskLimitShared,
28
28
  taskLimitPerType: patch.taskLimitPerType !== undefined ? patch.taskLimitPerType : current.taskLimitPerType,
29
+ storeAgentContext: patch.storeAgentContext ?? current.storeAgentContext,
29
30
  spendCurrency: patch.spendCurrency !== undefined ? patch.spendCurrency : current.spendCurrency,
30
31
  spendMonthlyLimit: patch.spendMonthlyLimit !== undefined ? patch.spendMonthlyLimit : current.spendMonthlyLimit,
31
32
  spendModelPrices: patch.spendModelPrices !== undefined ? patch.spendModelPrices : current.spendModelPrices,
@@ -1 +1 @@
1
- {"version":3,"file":"WorkspaceSettingsService.js","sourceRoot":"","sources":["../../../src/modules/settings/WorkspaceSettingsService.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAOlF;;;;;;GAMG;AACH,MAAM,OAAO,wBAAwB;IAClB,QAAQ,CAA6B;IACrC,mBAAmB,CAAqB;IAEzD,YAAY,IAA0C;QACpD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,2BAA2B,CAAA;QAChD,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,mBAAmB,CAAA;IACrD,CAAC;IAED,0FAA0F;IAC1F,KAAK,CAAC,GAAG,CAAC,WAAmB;QAC3B,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,GAAG,0BAA0B,EAAE,CAAA;IACpF,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,MAAM,CACV,WAAmB,EACnB,KAAmC;QAEnC,MAAM,gBAAgB,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;QAC7D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAC3C,MAAM,IAAI,GAAsB;YAC9B,wBAAwB,EAAE,KAAK,CAAC,wBAAwB,IAAI,OAAO,CAAC,wBAAwB;YAC5F,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,OAAO,CAAC,aAAa;YAC3D,eAAe,EACb,KAAK,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe;YACvF,gBAAgB,EACd,KAAK,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB;YAC1F,aAAa,EACX,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa;YACjF,iBAAiB,EACf,KAAK,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,iBAAiB;YAC7F,gBAAgB,EACd,KAAK,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB;SAC3F,CAAA;QACD,qFAAqF;QACrF,0CAA0C;QAC1C,IAAI,IAAI,CAAC,aAAa,KAAK,KAAK,EAAE,CAAC;YACjC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;YAC3B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;QAC9B,CAAC;aAAM,IAAI,IAAI,CAAC,aAAa,KAAK,QAAQ,EAAE,CAAC;YAC3C,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;YAC5B,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI;gBAAE,IAAI,CAAC,eAAe,GAAG,CAAC,CAAA;QAC5D,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;YAC3B,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI;gBAAE,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAA;QAC/D,CAAC;QACD,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;QAC7C,OAAO,IAAI,CAAA;IACb,CAAC;CACF"}
1
+ {"version":3,"file":"WorkspaceSettingsService.js","sourceRoot":"","sources":["../../../src/modules/settings/WorkspaceSettingsService.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAOlF;;;;;;GAMG;AACH,MAAM,OAAO,wBAAwB;IAClB,QAAQ,CAA6B;IACrC,mBAAmB,CAAqB;IAEzD,YAAY,IAA0C;QACpD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,2BAA2B,CAAA;QAChD,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,mBAAmB,CAAA;IACrD,CAAC;IAED,0FAA0F;IAC1F,KAAK,CAAC,GAAG,CAAC,WAAmB;QAC3B,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,GAAG,0BAA0B,EAAE,CAAA;IACpF,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,MAAM,CACV,WAAmB,EACnB,KAAmC;QAEnC,MAAM,gBAAgB,CAAC,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,CAAA;QAC7D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAC3C,MAAM,IAAI,GAAsB;YAC9B,wBAAwB,EAAE,KAAK,CAAC,wBAAwB,IAAI,OAAO,CAAC,wBAAwB;YAC5F,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,OAAO,CAAC,aAAa;YAC3D,eAAe,EACb,KAAK,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe;YACvF,gBAAgB,EACd,KAAK,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB;YAC1F,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,IAAI,OAAO,CAAC,iBAAiB;YACvE,aAAa,EACX,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa;YACjF,iBAAiB,EACf,KAAK,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,iBAAiB;YAC7F,gBAAgB,EACd,KAAK,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB;SAC3F,CAAA;QACD,qFAAqF;QACrF,0CAA0C;QAC1C,IAAI,IAAI,CAAC,aAAa,KAAK,KAAK,EAAE,CAAC;YACjC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;YAC3B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;QAC9B,CAAC;aAAM,IAAI,IAAI,CAAC,aAAa,KAAK,QAAQ,EAAE,CAAC;YAC3C,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;YAC5B,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI;gBAAE,IAAI,CAAC,eAAe,GAAG,CAAC,CAAA;QAC5D,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;YAC3B,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI;gBAAE,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAA;QAC/D,CAAC;QACD,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;QAC7C,OAAO,IAAI,CAAA;IACb,CAAC;CACF"}
@@ -0,0 +1,42 @@
1
+ /** A single problem found during validation. `error` aborts boot; `warn` is logged only. */
2
+ export interface RegistrationProblem {
3
+ severity: 'error' | 'warn';
4
+ code: string;
5
+ message: string;
6
+ }
7
+ /** Options for {@link collectRegistrationProblems} / {@link validateRegistrations}. */
8
+ export interface ValidateRegistrationsOptions {
9
+ /** Override the canonical result-view id set (defaults to contracts' {@link RESULT_VIEW_ID_SET}). */
10
+ knownResultViewIds?: ReadonlySet<string>;
11
+ /** Built-in helper kinds a gate may escalate to (defaults to ci-fixer/conflict-resolver/on-call). */
12
+ builtInHelperKinds?: ReadonlySet<string>;
13
+ /**
14
+ * The known built-in agent-kind ids, for validating a registered pipeline's `agentKinds`.
15
+ * The backend has no canonical runtime catalog of built-in kinds, so a pipeline-kind check
16
+ * is only run when this is supplied (else built-in kinds like `coder` would false-positive);
17
+ * unknown kinds are then ERRORS. Omitted ⇒ the pipeline-kind check is skipped.
18
+ */
19
+ knownAgentKinds?: ReadonlySet<string>;
20
+ /**
21
+ * Sink for `warn`-severity problems (orchestration is runtime-neutral, so it never touches
22
+ * `console`/a logger directly — the facade passes its logger). Omitted ⇒ warnings are dropped
23
+ * (errors still throw).
24
+ */
25
+ onWarn?: (problem: RegistrationProblem) => void;
26
+ }
27
+ /**
28
+ * Collect every registration problem (does not throw). Useful for tests and for callers that
29
+ * want to log warnings without aborting. {@link validateRegistrations} throws on any `error`.
30
+ */
31
+ export declare function collectRegistrationProblems(opts?: ValidateRegistrationsOptions): RegistrationProblem[];
32
+ /**
33
+ * Validate the registered extensions, throwing an aggregated error on any `error`-severity
34
+ * problem and logging `warn`-severity ones. Call once at facade boot, after every `register*`
35
+ * import side effect + provider wiring, before serving requests.
36
+ */
37
+ export declare function validateRegistrations(opts?: ValidateRegistrationsOptions): void;
38
+ /** Run {@link validateRegistrations} at most once per process. Safe to call from a per-request build. */
39
+ export declare function validateRegistrationsOnce(opts?: ValidateRegistrationsOptions): void;
40
+ /** Reset the once-guard. Intended for tests that exercise the boot path repeatedly. */
41
+ export declare function resetRegistrationValidationGuard(): void;
42
+ //# sourceMappingURL=validateRegistrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateRegistrations.d.ts","sourceRoot":"","sources":["../../src/validation/validateRegistrations.ts"],"names":[],"mappings":"AAqCA,4FAA4F;AAC5F,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,uFAAuF;AACvF,MAAM,WAAW,4BAA4B;IAC3C,qGAAqG;IACrG,kBAAkB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACxC,qGAAqG;IACrG,kBAAkB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACxC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACrC;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAA;CAChD;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,GAAE,4BAAiC,GACtC,mBAAmB,EAAE,CAgGvB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,4BAAiC,GAAG,IAAI,CAYnF;AAOD,yGAAyG;AACzG,wBAAgB,yBAAyB,CAAC,IAAI,GAAE,4BAAiC,GAAG,IAAI,CASvF;AAED,uFAAuF;AACvF,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD"}
@@ -0,0 +1,150 @@
1
+ import { registeredAgentKinds, registeredKindRequiresContainer, registeredStructuredOutput, } from '@cat-factory/agents';
2
+ import { CI_FIXER_AGENT_KIND, CONFLICT_RESOLVER_AGENT_KIND, ON_CALL_AGENT_KIND, registeredGateFactories, registeredPipelines, stubGateContext, } from '@cat-factory/kernel';
3
+ import { RESULT_VIEW_ID_SET } from '@cat-factory/contracts';
4
+ // ---------------------------------------------------------------------------
5
+ // Boot-time validation of the deployment's registered extensions (agent kinds, gates,
6
+ // pipelines). A typo'd gate `helperKind`, an unknown `resultView`, or a pipeline naming a
7
+ // kind that doesn't exist used to surface mid-run (a failed dispatch) or silently (a prose
8
+ // fallback). `validateRegistrations()` turns those into a LOUD startup error instead — a
9
+ // facade calls it once after all `register*` side-effect imports + provider wiring, before
10
+ // serving, so a misconfigured deployment fails fast at boot.
11
+ //
12
+ // This lives in orchestration because it cross-checks the gate registry (kernel) against the
13
+ // agent-kind registry (@cat-factory/agents) and the pipeline registry — only orchestration
14
+ // depends on all three.
15
+ // ---------------------------------------------------------------------------
16
+ /** Built-in container helper kinds a gate may escalate to (handled by the executor/harness,
17
+ * not the custom-kind registry). A gate `helperKind` is valid if it's one of these or a
18
+ * registered container-capable kind. */
19
+ const BUILT_IN_HELPER_KINDS = new Set([
20
+ CI_FIXER_AGENT_KIND,
21
+ CONFLICT_RESOLVER_AGENT_KIND,
22
+ ON_CALL_AGENT_KIND,
23
+ ]);
24
+ /**
25
+ * Collect every registration problem (does not throw). Useful for tests and for callers that
26
+ * want to log warnings without aborting. {@link validateRegistrations} throws on any `error`.
27
+ */
28
+ export function collectRegistrationProblems(opts = {}) {
29
+ const knownResultViewIds = opts.knownResultViewIds ?? RESULT_VIEW_ID_SET;
30
+ const builtInHelperKinds = opts.builtInHelperKinds ?? BUILT_IN_HELPER_KINDS;
31
+ const problems = [];
32
+ const agentKinds = registeredAgentKinds();
33
+ const registeredKindIds = new Set(agentKinds.map((d) => d.kind));
34
+ const gateKinds = new Set(registeredGateFactories().map((g) => g.kind));
35
+ // 1. Every gate's helperKind must resolve to a registered container-capable kind or a
36
+ // built-in helper. The factory is a pure constructor, so we build it with a stub context
37
+ // just to read its declared helperKind.
38
+ for (const { kind, factory } of registeredGateFactories()) {
39
+ let helperKind;
40
+ try {
41
+ helperKind = factory(stubGateContext()).helperKind;
42
+ }
43
+ catch (err) {
44
+ problems.push({
45
+ severity: 'error',
46
+ code: 'gate_factory_threw',
47
+ message: `Gate "${kind}" factory threw while validating: ${err.message}`,
48
+ });
49
+ continue;
50
+ }
51
+ const helperOk = builtInHelperKinds.has(helperKind) ||
52
+ (registeredKindIds.has(helperKind) && registeredKindRequiresContainer(helperKind));
53
+ if (!helperOk) {
54
+ problems.push({
55
+ severity: 'error',
56
+ code: 'gate_helper_unresolved',
57
+ message: `Gate "${kind}" escalates to helperKind "${helperKind}", which is neither a ` +
58
+ `built-in helper nor a registered container-capable agent kind. Register the helper ` +
59
+ `(a container surface) or fix the helperKind.`,
60
+ });
61
+ }
62
+ }
63
+ // 2. Every registered kind's presentation.resultView must be a known view id (else the SPA
64
+ // silently falls back to prose).
65
+ for (const def of agentKinds) {
66
+ const resultView = def.presentation?.resultView;
67
+ if (resultView !== undefined && !knownResultViewIds.has(resultView)) {
68
+ problems.push({
69
+ severity: 'error',
70
+ code: 'unknown_result_view',
71
+ message: `Agent kind "${def.kind}" declares resultView "${resultView}", which is not a known ` +
72
+ `result view. Use one of: ${[...knownResultViewIds].join(', ')}.`,
73
+ });
74
+ }
75
+ }
76
+ // 3. Coherence (warn): a kind with postOps that has an agent step which is NOT structured
77
+ // output likely can't feed those post-ops from `result.custom`. Heuristic, so a warning.
78
+ for (const def of agentKinds) {
79
+ const hasPostOps = (def.postOps?.length ?? 0) > 0;
80
+ const declaresStructured = def.agent?.output?.kind === 'structured' || registeredStructuredOutput(def.kind) !== undefined;
81
+ if (hasPostOps && def.agent && !declaresStructured) {
82
+ problems.push({
83
+ severity: 'warn',
84
+ code: 'postops_without_structured_output',
85
+ message: `Agent kind "${def.kind}" declares postOps but its agent step has no structured ` +
86
+ `output — postOps that read result.custom will see nothing. Declare structuredOutput ` +
87
+ `(or agent.output.kind: 'structured') if the post-op consumes the agent's JSON.`,
88
+ });
89
+ }
90
+ }
91
+ // 4. Pipeline kinds (only when a built-in catalog is supplied — see option doc).
92
+ if (opts.knownAgentKinds) {
93
+ const known = opts.knownAgentKinds;
94
+ for (const pipeline of registeredPipelines()) {
95
+ for (const agentKind of pipeline.agentKinds) {
96
+ const ok = known.has(agentKind) ||
97
+ registeredKindIds.has(agentKind) ||
98
+ gateKinds.has(agentKind) ||
99
+ builtInHelperKinds.has(agentKind);
100
+ if (!ok) {
101
+ problems.push({
102
+ severity: 'error',
103
+ code: 'pipeline_unknown_kind',
104
+ message: `Pipeline "${pipeline.id}" references agent kind "${agentKind}", which is not a ` +
105
+ `known built-in, a registered kind, or a registered gate.`,
106
+ });
107
+ }
108
+ }
109
+ }
110
+ }
111
+ return problems;
112
+ }
113
+ /**
114
+ * Validate the registered extensions, throwing an aggregated error on any `error`-severity
115
+ * problem and logging `warn`-severity ones. Call once at facade boot, after every `register*`
116
+ * import side effect + provider wiring, before serving requests.
117
+ */
118
+ export function validateRegistrations(opts = {}) {
119
+ const problems = collectRegistrationProblems(opts);
120
+ if (opts.onWarn) {
121
+ for (const w of problems.filter((p) => p.severity === 'warn'))
122
+ opts.onWarn(w);
123
+ }
124
+ const errors = problems.filter((p) => p.severity === 'error');
125
+ if (errors.length > 0) {
126
+ throw new Error(`Invalid extension registrations (${errors.length}):\n` +
127
+ errors.map((e) => ` - [${e.code}] ${e.message}`).join('\n'));
128
+ }
129
+ }
130
+ // A module-level guard so a per-request facade build (the Worker rebuilds its container per
131
+ // request) validates ONCE rather than on every request. Tests that intentionally register
132
+ // bogus kinds call `collectRegistrationProblems`/`validateRegistrations` directly instead.
133
+ let validated = false;
134
+ /** Run {@link validateRegistrations} at most once per process. Safe to call from a per-request build. */
135
+ export function validateRegistrationsOnce(opts = {}) {
136
+ if (validated)
137
+ return;
138
+ // Flip the guard only AFTER a clean validation. Setting it first would poison the guard on a
139
+ // throw: on the Worker (where this runs inside `fetch` on the first request) a misconfigured
140
+ // deployment would 500 exactly once, then — the module flag now `true` for the isolate's life —
141
+ // serve the broken config silently on every later request. Validating until it passes keeps the
142
+ // failure loud (every request re-throws) until the deployment is fixed, matching the boot intent.
143
+ validateRegistrations(opts);
144
+ validated = true;
145
+ }
146
+ /** Reset the once-guard. Intended for tests that exercise the boot path repeatedly. */
147
+ export function resetRegistrationValidationGuard() {
148
+ validated = false;
149
+ }
150
+ //# sourceMappingURL=validateRegistrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateRegistrations.js","sourceRoot":"","sources":["../../src/validation/validateRegistrations.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/orchestration",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Delivery-workflow engine for the Agent Architecture Board (execution, bootstrap, pipelines, board, boardScan, requirements, and composition root).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,14 +25,14 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "ai": "^6.0.209",
28
- "@cat-factory/agents": "0.14.9",
29
- "@cat-factory/contracts": "0.26.0",
30
- "@cat-factory/integrations": "0.21.0",
31
- "@cat-factory/kernel": "0.29.0",
32
- "@cat-factory/prompt-fragments": "0.7.24",
33
- "@cat-factory/sandbox": "0.8.2",
34
- "@cat-factory/spend": "0.9.3",
35
- "@cat-factory/workspaces": "0.7.36"
28
+ "@cat-factory/agents": "0.15.1",
29
+ "@cat-factory/contracts": "0.28.0",
30
+ "@cat-factory/integrations": "0.21.2",
31
+ "@cat-factory/kernel": "0.31.0",
32
+ "@cat-factory/prompt-fragments": "0.7.26",
33
+ "@cat-factory/sandbox": "0.8.4",
34
+ "@cat-factory/spend": "0.9.5",
35
+ "@cat-factory/workspaces": "0.7.38"
36
36
  },
37
37
  "devDependencies": {
38
38
  "typescript": "7.0.1-rc",