@renseiai/agentfactory 0.8.18 → 0.8.19

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.
Files changed (45) hide show
  1. package/dist/src/governor/decision-engine-adapter.d.ts +43 -0
  2. package/dist/src/governor/decision-engine-adapter.d.ts.map +1 -0
  3. package/dist/src/governor/decision-engine-adapter.js +422 -0
  4. package/dist/src/governor/decision-engine-adapter.test.d.ts +2 -0
  5. package/dist/src/governor/decision-engine-adapter.test.d.ts.map +1 -0
  6. package/dist/src/governor/decision-engine-adapter.test.js +363 -0
  7. package/dist/src/governor/index.d.ts +1 -0
  8. package/dist/src/governor/index.d.ts.map +1 -1
  9. package/dist/src/governor/index.js +1 -0
  10. package/dist/src/manifest/route-manifest.d.ts.map +1 -1
  11. package/dist/src/manifest/route-manifest.js +4 -0
  12. package/dist/src/orchestrator/orchestrator.d.ts +27 -0
  13. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
  14. package/dist/src/orchestrator/orchestrator.js +289 -86
  15. package/dist/src/providers/claude-provider.d.ts.map +1 -1
  16. package/dist/src/providers/claude-provider.js +11 -0
  17. package/dist/src/providers/codex-app-server-provider.d.ts +201 -0
  18. package/dist/src/providers/codex-app-server-provider.d.ts.map +1 -0
  19. package/dist/src/providers/codex-app-server-provider.js +786 -0
  20. package/dist/src/providers/codex-app-server-provider.test.d.ts +2 -0
  21. package/dist/src/providers/codex-app-server-provider.test.d.ts.map +1 -0
  22. package/dist/src/providers/codex-app-server-provider.test.js +529 -0
  23. package/dist/src/providers/codex-provider.d.ts +24 -4
  24. package/dist/src/providers/codex-provider.d.ts.map +1 -1
  25. package/dist/src/providers/codex-provider.js +58 -6
  26. package/dist/src/providers/index.d.ts +1 -0
  27. package/dist/src/providers/index.d.ts.map +1 -1
  28. package/dist/src/providers/index.js +1 -0
  29. package/dist/src/routing/observation-recorder.test.js +1 -1
  30. package/dist/src/routing/observation-store.d.ts +15 -1
  31. package/dist/src/routing/observation-store.d.ts.map +1 -1
  32. package/dist/src/routing/observation-store.test.js +17 -11
  33. package/dist/src/templates/index.d.ts +2 -1
  34. package/dist/src/templates/index.d.ts.map +1 -1
  35. package/dist/src/templates/index.js +1 -0
  36. package/dist/src/templates/registry.d.ts +23 -0
  37. package/dist/src/templates/registry.d.ts.map +1 -1
  38. package/dist/src/templates/registry.js +80 -0
  39. package/dist/src/templates/schema.d.ts +31 -0
  40. package/dist/src/templates/schema.d.ts.map +1 -0
  41. package/dist/src/templates/schema.js +139 -0
  42. package/dist/src/templates/schema.test.d.ts +2 -0
  43. package/dist/src/templates/schema.test.d.ts.map +1 -0
  44. package/dist/src/templates/schema.test.js +215 -0
  45. package/package.json +2 -2
@@ -1,10 +1,22 @@
1
1
  /**
2
2
  * OpenAI Codex Agent Provider
3
3
  *
4
- * Spawns the `codex` CLI (from @openai/codex) as a child process and parses
5
- * its JSONL event stream into normalized AgentEvents.
4
+ * Unified Codex provider with two execution modes:
6
5
  *
7
- * CLI invocation patterns:
6
+ * 1. **App Server mode** (default when CODEX_USE_APP_SERVER=1):
7
+ * Uses a long-lived `codex app-server` process communicating via JSON-RPC 2.0
8
+ * over stdio. Supports multiple concurrent threads on a single process.
9
+ *
10
+ * 2. **Exec fallback mode** (backward compatibility):
11
+ * Spawns the `codex` CLI as a child process and parses its JSONL event stream.
12
+ * Used for CI/CD or when app-server is unavailable.
13
+ *
14
+ * Mode selection:
15
+ * - CODEX_USE_APP_SERVER=1 → App Server mode (JSON-RPC 2.0)
16
+ * - CODEX_USE_APP_SERVER=0 → Exec fallback mode (CLI JSONL)
17
+ * - Not set → Exec fallback mode (default, backward compatible)
18
+ *
19
+ * Exec CLI invocation patterns:
8
20
  * New session: codex exec --json --full-auto -C <cwd> "<prompt>"
9
21
  * Resume: codex exec resume --json --full-auto <session_id> "<prompt>"
10
22
  *
@@ -18,6 +30,7 @@
18
30
  */
19
31
  import { spawn } from 'child_process';
20
32
  import { createInterface } from 'readline';
33
+ import { CodexAppServerProvider } from './codex-app-server-provider.js';
21
34
  /**
22
35
  * Map a single Codex JSONL event to one or more normalized AgentEvents.
23
36
  * Exported for unit testing — the AgentHandle uses this internally.
@@ -186,19 +199,58 @@ export function mapCodexItemEvent(event) {
186
199
  // ---------------------------------------------------------------------------
187
200
  // Provider
188
201
  // ---------------------------------------------------------------------------
202
+ /**
203
+ * Check whether App Server mode is enabled.
204
+ *
205
+ * Returns true when CODEX_USE_APP_SERVER=1 (or 'true').
206
+ * The env var can be set globally or per-spawn via config.env.
207
+ */
208
+ function isAppServerEnabled(config) {
209
+ const envVal = config?.env?.CODEX_USE_APP_SERVER
210
+ ?? process.env.CODEX_USE_APP_SERVER
211
+ ?? '';
212
+ return envVal === '1' || envVal.toLowerCase() === 'true';
213
+ }
189
214
  export class CodexProvider {
190
215
  name = 'codex';
191
216
  capabilities = {
192
217
  supportsMessageInjection: false,
193
218
  supportsSessionResume: true,
194
219
  };
220
+ /** App Server delegate — created lazily when App Server mode is enabled */
221
+ appServerProvider = null;
195
222
  spawn(config) {
196
- return this.createHandle(config);
223
+ if (isAppServerEnabled(config)) {
224
+ return this.getAppServerProvider().spawn(config);
225
+ }
226
+ return this.createExecHandle(config);
197
227
  }
198
228
  resume(sessionId, config) {
199
- return this.createHandle(config, sessionId);
229
+ if (isAppServerEnabled(config)) {
230
+ return this.getAppServerProvider().resume(sessionId, config);
231
+ }
232
+ return this.createExecHandle(config, sessionId);
233
+ }
234
+ /**
235
+ * Shut down the App Server process if running.
236
+ * No-op if in exec fallback mode.
237
+ */
238
+ async shutdown() {
239
+ if (this.appServerProvider) {
240
+ await this.appServerProvider.shutdown();
241
+ this.appServerProvider = null;
242
+ }
243
+ }
244
+ getAppServerProvider() {
245
+ if (!this.appServerProvider) {
246
+ this.appServerProvider = new CodexAppServerProvider();
247
+ }
248
+ return this.appServerProvider;
200
249
  }
201
- createHandle(config, resumeSessionId) {
250
+ // -------------------------------------------------------------------------
251
+ // Exec fallback mode (backward compatibility — SUP-1739)
252
+ // -------------------------------------------------------------------------
253
+ createExecHandle(config, resumeSessionId) {
202
254
  const abortController = config.abortController;
203
255
  // Resolve the codex binary — prefer CODEX_BIN env var, then fall back to 'codex'
204
256
  const codexBin = config.env.CODEX_BIN || process.env.CODEX_BIN || 'codex';
@@ -32,6 +32,7 @@ export type { AgentInitEvent, AgentSystemEvent, AgentAssistantTextEvent, AgentTo
32
32
  export type { ActionDefinition, ActionResult, AuthResult, ConditionContext, ConditionDefinition, CredentialDefinition, CredentialField, DynamicOptionField, NormalizedEvent, ProviderExecutionContext, ProviderPlugin, TriggerDefinition, WebhookConfig, WebhookRegistration, } from './plugin-types.js';
33
33
  export { ClaudeProvider, createClaudeProvider } from './claude-provider.js';
34
34
  export { CodexProvider, createCodexProvider } from './codex-provider.js';
35
+ export { CodexAppServerProvider, createCodexAppServerProvider } from './codex-app-server-provider.js';
35
36
  export { AmpProvider, createAmpProvider } from './amp-provider.js';
36
37
  export { SpringAiProvider, createSpringAiProvider } from './spring-ai-provider.js';
37
38
  export { A2aProvider, createA2aProvider } from './a2a-provider.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/index.ts"],"names":[],"mappings":"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"}
@@ -29,6 +29,7 @@
29
29
  */
30
30
  export { ClaudeProvider, createClaudeProvider } from './claude-provider.js';
31
31
  export { CodexProvider, createCodexProvider } from './codex-provider.js';
32
+ export { CodexAppServerProvider, createCodexAppServerProvider } from './codex-app-server-provider.js';
32
33
  export { AmpProvider, createAmpProvider } from './amp-provider.js';
33
34
  export { SpringAiProvider, createSpringAiProvider } from './spring-ai-provider.js';
34
35
  export { A2aProvider, createA2aProvider } from './a2a-provider.js';
@@ -14,7 +14,7 @@ function makeProcess(overrides = {}) {
14
14
  function makeMockStores() {
15
15
  const observationStore = {
16
16
  recordObservation: vi.fn().mockResolvedValue(undefined),
17
- getObservations: vi.fn().mockResolvedValue([]),
17
+ getObservations: vi.fn().mockResolvedValue({ observations: [] }),
18
18
  getRecentObservations: vi.fn().mockResolvedValue([]),
19
19
  };
20
20
  const posteriorStore = {
@@ -1,6 +1,14 @@
1
1
  import type { AgentProviderName } from '../providers/types.js';
2
2
  import type { AgentWorkType } from '../orchestrator/work-types.js';
3
3
  import type { RoutingObservation } from './types.js';
4
+ /**
5
+ * Result of a paginated observation query.
6
+ */
7
+ export interface ObservationQueryResult {
8
+ observations: RoutingObservation[];
9
+ /** Opaque cursor for fetching the next page. Undefined when no more results. */
10
+ nextCursor?: string;
11
+ }
4
12
  /**
5
13
  * Observation Store Interface
6
14
  *
@@ -20,13 +28,19 @@ export interface ObservationStore {
20
28
  * @param opts.workType - Filter by work type
21
29
  * @param opts.limit - Maximum number of observations to return
22
30
  * @param opts.since - Only return observations with timestamp >= since (epoch ms)
31
+ * @param opts.from - Start of time range (epoch ms), inclusive
32
+ * @param opts.to - End of time range (epoch ms), inclusive
33
+ * @param opts.cursor - Opaque cursor for pagination (Redis Stream ID)
23
34
  */
24
35
  getObservations(opts: {
25
36
  provider?: AgentProviderName;
26
37
  workType?: AgentWorkType;
27
38
  limit?: number;
28
39
  since?: number;
29
- }): Promise<RoutingObservation[]>;
40
+ from?: number;
41
+ to?: number;
42
+ cursor?: string;
43
+ }): Promise<ObservationQueryResult>;
30
44
  /**
31
45
  * Get the most recent observations for a specific provider + work type pair.
32
46
  * Results are returned newest-first, up to `windowSize` entries.
@@ -1 +1 @@
1
- {"version":3,"file":"observation-store.d.ts","sourceRoot":"","sources":["../../../src/routing/observation-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,iBAAiB,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEzD;;;;;;;OAOG;IACH,eAAe,CAAC,IAAI,EAAE;QACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAA;QAC5B,QAAQ,CAAC,EAAE,aAAa,CAAA;QACxB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAEjC;;;;;;;OAOG;IACH,qBAAqB,CACnB,QAAQ,EAAE,iBAAiB,EAC3B,QAAQ,EAAE,aAAa,EACvB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;CACjC"}
1
+ {"version":3,"file":"observation-store.d.ts","sourceRoot":"","sources":["../../../src/routing/observation-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,kBAAkB,EAAE,CAAA;IAClC,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,iBAAiB,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEzD;;;;;;;;;;OAUG;IACH,eAAe,CAAC,IAAI,EAAE;QACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAA;QAC5B,QAAQ,CAAC,EAAE,aAAa,CAAA;QACxB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAA;IAEnC;;;;;;;OAOG;IACH,qBAAqB,CACnB,QAAQ,EAAE,iBAAiB,EAC3B,QAAQ,EAAE,aAAa,EACvB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;CACjC"}
@@ -41,10 +41,16 @@ function createInMemoryStore() {
41
41
  if (opts.since) {
42
42
  result = result.filter((o) => o.timestamp >= opts.since);
43
43
  }
44
+ if (opts.from) {
45
+ result = result.filter((o) => o.timestamp >= opts.from);
46
+ }
47
+ if (opts.to) {
48
+ result = result.filter((o) => o.timestamp <= opts.to);
49
+ }
44
50
  if (opts.limit) {
45
51
  result = result.slice(0, opts.limit);
46
52
  }
47
- return result;
53
+ return { observations: result };
48
54
  },
49
55
  async getRecentObservations(provider, workType, windowSize) {
50
56
  return observations
@@ -67,8 +73,8 @@ describe('ObservationStore interface', () => {
67
73
  const obs = makeObservation();
68
74
  await store.recordObservation(obs);
69
75
  const all = await store.getObservations({});
70
- expect(all).toHaveLength(1);
71
- expect(all[0]).toEqual(obs);
76
+ expect(all.observations).toHaveLength(1);
77
+ expect(all.observations[0]).toEqual(obs);
72
78
  });
73
79
  it('getObservations filters by provider', async () => {
74
80
  const store = createInMemoryStore();
@@ -76,16 +82,16 @@ describe('ObservationStore interface', () => {
76
82
  await store.recordObservation(makeObservation({ provider: 'codex' }));
77
83
  await store.recordObservation(makeObservation({ provider: 'claude' }));
78
84
  const result = await store.getObservations({ provider: 'claude' });
79
- expect(result).toHaveLength(2);
80
- expect(result.every((o) => o.provider === 'claude')).toBe(true);
85
+ expect(result.observations).toHaveLength(2);
86
+ expect(result.observations.every((o) => o.provider === 'claude')).toBe(true);
81
87
  });
82
88
  it('getObservations filters by workType', async () => {
83
89
  const store = createInMemoryStore();
84
90
  await store.recordObservation(makeObservation({ workType: 'development' }));
85
91
  await store.recordObservation(makeObservation({ workType: 'qa' }));
86
92
  const result = await store.getObservations({ workType: 'qa' });
87
- expect(result).toHaveLength(1);
88
- expect(result[0].workType).toBe('qa');
93
+ expect(result.observations).toHaveLength(1);
94
+ expect(result.observations[0].workType).toBe('qa');
89
95
  });
90
96
  it('getObservations filters by since', async () => {
91
97
  const store = createInMemoryStore();
@@ -93,8 +99,8 @@ describe('ObservationStore interface', () => {
93
99
  await store.recordObservation(makeObservation({ timestamp: 2000 }));
94
100
  await store.recordObservation(makeObservation({ timestamp: 3000 }));
95
101
  const result = await store.getObservations({ since: 2000 });
96
- expect(result).toHaveLength(2);
97
- expect(result.every((o) => o.timestamp >= 2000)).toBe(true);
102
+ expect(result.observations).toHaveLength(2);
103
+ expect(result.observations.every((o) => o.timestamp >= 2000)).toBe(true);
98
104
  });
99
105
  it('getObservations respects limit', async () => {
100
106
  const store = createInMemoryStore();
@@ -102,7 +108,7 @@ describe('ObservationStore interface', () => {
102
108
  await store.recordObservation(makeObservation({ timestamp: i }));
103
109
  }
104
110
  const result = await store.getObservations({ limit: 3 });
105
- expect(result).toHaveLength(3);
111
+ expect(result.observations).toHaveLength(3);
106
112
  });
107
113
  it('getRecentObservations returns newest-first for a provider+workType pair', async () => {
108
114
  const store = createInMemoryStore();
@@ -128,7 +134,7 @@ describe('ObservationStore interface', () => {
128
134
  const store = createInMemoryStore();
129
135
  await store.recordObservation(makeObservation({ provider: 'claude' }));
130
136
  const result = await store.getObservations({ provider: 'codex' });
131
- expect(result).toEqual([]);
137
+ expect(result.observations).toEqual([]);
132
138
  });
133
139
  it('getRecentObservations returns empty array when no observations match', async () => {
134
140
  const store = createInMemoryStore();
@@ -5,7 +5,8 @@
5
5
  */
6
6
  export type { WorkflowTemplate, PartialTemplate, TemplateContext, ToolPermission, ToolPermissionAdapter, TemplateRegistryConfig, } from './types.js';
7
7
  export { WorkflowTemplateSchema, PartialTemplateSchema, TemplateContextSchema, AgentWorkTypeSchema, ToolPermissionSchema, validateWorkflowTemplate, validatePartialTemplate, } from './types.js';
8
- export { TemplateRegistry } from './registry.js';
8
+ export { TemplateRegistry, type TemplateValidationResult } from './registry.js';
9
+ export { generateTemplateSchema, extractTemplateVariables, type TemplateSchemaOptions } from './schema.js';
9
10
  export { loadTemplatesFromDir, loadTemplateFile, loadPartialsFromDir, getBuiltinDefaultsDir, getBuiltinPartialsDir, } from './loader.js';
10
11
  export { ClaudeToolPermissionAdapter, CodexToolPermissionAdapter, createToolPermissionAdapter } from './adapters.js';
11
12
  export { renderPromptWithFallback } from './renderer.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,2BAA2B,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;AAEpH,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA;AAExD,YAAY,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AACxF,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,gBAAgB,EAAE,KAAK,wBAAwB,EAAE,MAAM,eAAe,CAAA;AAE/E,OAAO,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAE1G,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,2BAA2B,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;AAEpH,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA;AAExD,YAAY,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AACxF,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAA"}
@@ -5,6 +5,7 @@
5
5
  */
6
6
  export { WorkflowTemplateSchema, PartialTemplateSchema, TemplateContextSchema, AgentWorkTypeSchema, ToolPermissionSchema, validateWorkflowTemplate, validatePartialTemplate, } from './types.js';
7
7
  export { TemplateRegistry } from './registry.js';
8
+ export { generateTemplateSchema, extractTemplateVariables } from './schema.js';
8
9
  export { loadTemplatesFromDir, loadTemplateFile, loadPartialsFromDir, getBuiltinDefaultsDir, getBuiltinPartialsDir, } from './loader.js';
9
10
  export { ClaudeToolPermissionAdapter, CodexToolPermissionAdapter, createToolPermissionAdapter } from './adapters.js';
10
11
  export { renderPromptWithFallback } from './renderer.js';
@@ -5,8 +5,10 @@
5
5
  * Supports layered resolution with built-in defaults, project overrides,
6
6
  * and inline config overrides.
7
7
  */
8
+ import type { JSONSchema7 } from 'json-schema';
8
9
  import type { AgentWorkType } from '../orchestrator/work-types.js';
9
10
  import type { WorkflowTemplate, TemplateContext, TemplateRegistryConfig, ToolPermission, ToolPermissionAdapter } from './types.js';
11
+ import { type TemplateSchemaOptions } from './schema.js';
10
12
  /**
11
13
  * Template Registry manages workflow templates and renders prompts.
12
14
  *
@@ -76,5 +78,26 @@ export declare class TemplateRegistry {
76
78
  * Register a partial template for use in Handlebars rendering.
77
79
  */
78
80
  registerPartial(name: string, content: string): void;
81
+ /**
82
+ * Get JSON Schema 7 for a template's parameters.
83
+ *
84
+ * The schema is derived from the template's prompt by extracting
85
+ * Handlebars expression references and mapping them to the known
86
+ * TemplateContext fields. Returns null if no template is registered.
87
+ */
88
+ getSchema(templateName: string, options?: TemplateSchemaOptions): JSONSchema7 | null;
89
+ /**
90
+ * Validate a config object against a template's JSON Schema.
91
+ *
92
+ * Template expressions ({{ }}) in string values are treated as valid
93
+ * placeholders and are not validated against the schema type.
94
+ *
95
+ * Returns a validation result with `valid` boolean and any `errors`.
96
+ */
97
+ validateConfig(templateName: string, config: Record<string, unknown>): TemplateValidationResult;
98
+ }
99
+ export interface TemplateValidationResult {
100
+ valid: boolean;
101
+ errors: string[];
79
102
  }
80
103
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/templates/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,qBAAqB,EACtB,MAAM,YAAY,CAAA;AAQnB;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAC3B;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,qBAAqB,CAAC,CAAuB;;IAcrD;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,GAAE,sBAA2B,GAAG,gBAAgB;IAMpE;;;;;;OAMG;IACH,UAAU,CAAC,MAAM,GAAE,sBAA2B,GAAG,IAAI;IA4CrD;;OAEG;IACH,wBAAwB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI;IAI9D;;;;;;;;OAQG;IACH,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IASrF;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO;IAQhE;;OAEG;IACH,sBAAsB,IAAI,MAAM,EAAE;IAIlC;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAkBjG;;;OAGG;IACH,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAgBpF;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,GAAG,SAAS;IAK5F;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;CAGrD"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/templates/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,qBAAqB,EACtB,MAAM,YAAY,CAAA;AAOnB,OAAO,EAA0B,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEhF;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAC3B;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,qBAAqB,CAAC,CAAuB;;IAcrD;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,GAAE,sBAA2B,GAAG,gBAAgB;IAMpE;;;;;;OAMG;IACH,UAAU,CAAC,MAAM,GAAE,sBAA2B,GAAG,IAAI;IA4CrD;;OAEG;IACH,wBAAwB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI;IAI9D;;;;;;;;OAQG;IACH,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IASrF;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO;IAQhE;;OAEG;IACH,sBAAsB,IAAI,MAAM,EAAE;IAIlC;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAkBjG;;;OAGG;IACH,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAgBpF;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,GAAG,SAAS;IAK5F;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAIpD;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,WAAW,GAAG,IAAI;IAMpF;;;;;;;OAOG;IACH,cAAc,CACZ,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,wBAAwB;CAS5B;AAMD,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB"}
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import Handlebars from 'handlebars';
9
9
  import { loadTemplatesFromDir, loadPartialsFromDir, getBuiltinDefaultsDir, getBuiltinPartialsDir, } from './loader.js';
10
+ import { generateTemplateSchema } from './schema.js';
10
11
  /**
11
12
  * Template Registry manages workflow templates and renders prompts.
12
13
  *
@@ -174,4 +175,83 @@ export class TemplateRegistry {
174
175
  registerPartial(name, content) {
175
176
  this.handlebars.registerPartial(name, content);
176
177
  }
178
+ /**
179
+ * Get JSON Schema 7 for a template's parameters.
180
+ *
181
+ * The schema is derived from the template's prompt by extracting
182
+ * Handlebars expression references and mapping them to the known
183
+ * TemplateContext fields. Returns null if no template is registered.
184
+ */
185
+ getSchema(templateName, options) {
186
+ const template = this.templates.get(templateName);
187
+ if (!template)
188
+ return null;
189
+ return generateTemplateSchema(template, options);
190
+ }
191
+ /**
192
+ * Validate a config object against a template's JSON Schema.
193
+ *
194
+ * Template expressions ({{ }}) in string values are treated as valid
195
+ * placeholders and are not validated against the schema type.
196
+ *
197
+ * Returns a validation result with `valid` boolean and any `errors`.
198
+ */
199
+ validateConfig(templateName, config) {
200
+ const template = this.templates.get(templateName);
201
+ if (!template) {
202
+ return { valid: false, errors: [`Template "${templateName}" not found`] };
203
+ }
204
+ const schema = generateTemplateSchema(template);
205
+ return validateConfigAgainstSchema(config, schema);
206
+ }
207
+ }
208
+ /** Handlebars expression pattern: {{ ... }} */
209
+ const TEMPLATE_EXPRESSION_RE = /^\{\{.*\}\}$/s;
210
+ /**
211
+ * Validate a config object against a JSON Schema 7.
212
+ * Template expressions ({{ }}) are treated as valid for any field.
213
+ */
214
+ function validateConfigAgainstSchema(config, schema) {
215
+ const errors = [];
216
+ const properties = schema.properties ?? {};
217
+ const required = schema.required ?? [];
218
+ // Check required fields
219
+ for (const field of required) {
220
+ if (!(field in config)) {
221
+ errors.push(`Missing required field: "${field}"`);
222
+ }
223
+ }
224
+ // Type-check provided fields
225
+ for (const [key, value] of Object.entries(config)) {
226
+ const fieldSchema = properties[key];
227
+ if (!fieldSchema || typeof fieldSchema === 'boolean')
228
+ continue;
229
+ // Template expressions are always valid
230
+ if (typeof value === 'string' && TEMPLATE_EXPRESSION_RE.test(value.trim())) {
231
+ continue;
232
+ }
233
+ const typeError = checkType(key, value, fieldSchema);
234
+ if (typeError)
235
+ errors.push(typeError);
236
+ }
237
+ return { valid: errors.length === 0, errors };
238
+ }
239
+ function checkType(key, value, schema) {
240
+ const schemaType = schema.type;
241
+ if (!schemaType)
242
+ return null;
243
+ // Normalize to array of type strings
244
+ const types = Array.isArray(schemaType) ? schemaType : [schemaType];
245
+ const valueType = Array.isArray(value) ? 'array' : typeof value;
246
+ // null is a valid JSON Schema type
247
+ if (value === null && types.includes('null'))
248
+ return null;
249
+ if (value === null)
250
+ return `Field "${key}" is null but expected ${types.join(' | ')}`;
251
+ // 'integer' matches typeof 'number' in JS
252
+ const effectiveTypes = types.map(t => t === 'integer' ? 'number' : t);
253
+ if (!effectiveTypes.includes(valueType)) {
254
+ return `Field "${key}" has type "${valueType}" but expected ${types.join(' | ')}`;
255
+ }
256
+ return null;
177
257
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Template JSON Schema Generation
3
+ *
4
+ * Generates JSON Schema 7 definitions from WorkflowTemplate objects.
5
+ * Schema is derived from TemplateContext fields referenced in the template's
6
+ * prompt via Handlebars expressions.
7
+ *
8
+ * @see SUP-1758
9
+ */
10
+ import type { JSONSchema7 } from 'json-schema';
11
+ import type { WorkflowTemplate } from './types.js';
12
+ export interface TemplateSchemaOptions {
13
+ /** Include all TemplateContext fields, not just those referenced in the prompt */
14
+ includeAllFields?: boolean;
15
+ }
16
+ /**
17
+ * Extract Handlebars variable references from a template prompt.
18
+ * Matches {{ varName }}, {{ varName.property }}, and handles
19
+ * conditionals like {{#if varName}} and {{#unless varName}}.
20
+ *
21
+ * Returns the set of top-level variable names referenced.
22
+ */
23
+ export declare function extractTemplateVariables(prompt: string): Set<string>;
24
+ /**
25
+ * Generate a JSON Schema 7 definition for a WorkflowTemplate's parameters.
26
+ *
27
+ * By default, only includes fields that are referenced in the template's
28
+ * prompt. Set `includeAllFields: true` to include all known TemplateContext fields.
29
+ */
30
+ export declare function generateTemplateSchema(template: WorkflowTemplate, options?: TemplateSchemaOptions): JSONSchema7;
31
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/templates/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAyB,MAAM,aAAa,CAAA;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAMlD,MAAM,WAAW,qBAAqB;IACpC,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AA4DD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA2BpE;AAMD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,WAAW,CAqCb"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Template JSON Schema Generation
3
+ *
4
+ * Generates JSON Schema 7 definitions from WorkflowTemplate objects.
5
+ * Schema is derived from TemplateContext fields referenced in the template's
6
+ * prompt via Handlebars expressions.
7
+ *
8
+ * @see SUP-1758
9
+ */
10
+ // ---------------------------------------------------------------------------
11
+ // Known TemplateContext field definitions
12
+ // ---------------------------------------------------------------------------
13
+ /**
14
+ * Maps TemplateContext field names to their JSON Schema 7 definitions.
15
+ * This is the source of truth for schema generation.
16
+ */
17
+ const CONTEXT_FIELD_SCHEMAS = {
18
+ identifier: { type: 'string', description: 'Issue identifier, e.g., "SUP-123"' },
19
+ mentionContext: { type: 'string', description: 'Optional user mention text providing additional context' },
20
+ startStatus: { type: 'string', description: 'Status to show when agent starts, e.g., "Started"' },
21
+ completeStatus: { type: 'string', description: 'Status to show when agent completes, e.g., "Finished"' },
22
+ parentContext: { type: 'string', description: 'Pre-built enriched prompt for parent issues with sub-issues' },
23
+ subIssueList: { type: 'string', description: 'Formatted list of sub-issues with statuses' },
24
+ cycleCount: { type: 'integer', description: 'Current escalation cycle count' },
25
+ strategy: { type: 'string', description: 'Current escalation strategy' },
26
+ failureSummary: { type: 'string', description: 'Accumulated failure summary across cycles' },
27
+ attemptNumber: { type: 'integer', description: 'Attempt number within current phase' },
28
+ previousFailureReasons: {
29
+ type: 'array',
30
+ items: { type: 'string' },
31
+ description: 'List of previous failure reasons',
32
+ },
33
+ totalCostUsd: { type: 'number', description: 'Total cost in USD across all attempts' },
34
+ blockerIdentifier: { type: 'string', description: 'Blocker issue identifier' },
35
+ team: { type: 'string', description: 'Team name' },
36
+ repository: { type: 'string', description: 'Git repository URL pattern' },
37
+ projectPath: { type: 'string', description: 'Root directory for this project within the repo' },
38
+ sharedPaths: {
39
+ type: 'array',
40
+ items: { type: 'string' },
41
+ description: 'Shared directories that any project agent may modify',
42
+ },
43
+ useToolPlugins: { type: 'boolean', description: 'When true, agents use in-process tools instead of CLI' },
44
+ linearCli: { type: 'string', description: 'Command to invoke the Linear CLI (default: "pnpm af-linear")' },
45
+ packageManager: { type: 'string', description: 'Package manager used by the project (default: "pnpm")' },
46
+ buildCommand: { type: 'string', description: 'Build command override' },
47
+ testCommand: { type: 'string', description: 'Test command override' },
48
+ validateCommand: { type: 'string', description: 'Validation command override' },
49
+ phaseOutputs: {
50
+ type: 'object',
51
+ additionalProperties: {
52
+ type: 'object',
53
+ additionalProperties: true,
54
+ },
55
+ description: 'Collected outputs from upstream phases',
56
+ },
57
+ agentBugBacklog: { type: 'string', description: 'Linear project name for agent-improvement issues' },
58
+ };
59
+ /** Fields that are always required in a template config */
60
+ const ALWAYS_REQUIRED = ['identifier'];
61
+ // ---------------------------------------------------------------------------
62
+ // Handlebars expression extraction
63
+ // ---------------------------------------------------------------------------
64
+ /**
65
+ * Extract Handlebars variable references from a template prompt.
66
+ * Matches {{ varName }}, {{ varName.property }}, and handles
67
+ * conditionals like {{#if varName}} and {{#unless varName}}.
68
+ *
69
+ * Returns the set of top-level variable names referenced.
70
+ */
71
+ export function extractTemplateVariables(prompt) {
72
+ const vars = new Set();
73
+ // Match {{ expr }}, {{#if expr}}, {{#unless expr}}, {{> partial}}, etc.
74
+ const patterns = [
75
+ /\{\{\s*([a-zA-Z_][a-zA-Z0-9_.]*)\s*\}\}/g, // {{ varName }} or {{ var.prop }}
76
+ /\{\{#(?:if|unless)\s+([a-zA-Z_][a-zA-Z0-9_.]*)/g, // {{#if varName}}
77
+ /\{\{#(?:each)\s+([a-zA-Z_][a-zA-Z0-9_.]*)/g, // {{#each varName}}
78
+ /\{\{#(?:with)\s+([a-zA-Z_][a-zA-Z0-9_.]*)/g, // {{#with varName}}
79
+ /\{\{\s*(?:eq|neq)\s+([a-zA-Z_][a-zA-Z0-9_.]*)/g, // {{ eq varName "value" }}
80
+ /\((?:eq|neq)\s+([a-zA-Z_][a-zA-Z0-9_.]*)/g, // (eq varName "value") — subexpression
81
+ ];
82
+ for (const pattern of patterns) {
83
+ let match;
84
+ while ((match = pattern.exec(prompt)) !== null) {
85
+ const varName = match[1];
86
+ // Extract the top-level variable name (before any dot)
87
+ const topLevel = varName.split('.')[0];
88
+ // Skip Handlebars built-ins and partials
89
+ if (topLevel !== 'this' && topLevel !== 'else') {
90
+ vars.add(topLevel);
91
+ }
92
+ }
93
+ }
94
+ return vars;
95
+ }
96
+ // ---------------------------------------------------------------------------
97
+ // Schema generation
98
+ // ---------------------------------------------------------------------------
99
+ /**
100
+ * Generate a JSON Schema 7 definition for a WorkflowTemplate's parameters.
101
+ *
102
+ * By default, only includes fields that are referenced in the template's
103
+ * prompt. Set `includeAllFields: true` to include all known TemplateContext fields.
104
+ */
105
+ export function generateTemplateSchema(template, options) {
106
+ const includeAll = options?.includeAllFields ?? false;
107
+ const properties = {};
108
+ const required = [];
109
+ if (includeAll) {
110
+ // Include all known fields
111
+ for (const [field, schemaDef] of Object.entries(CONTEXT_FIELD_SCHEMAS)) {
112
+ properties[field] = schemaDef;
113
+ }
114
+ }
115
+ else {
116
+ // Only include fields referenced in the template prompt
117
+ const referencedVars = extractTemplateVariables(template.prompt);
118
+ for (const varName of referencedVars) {
119
+ if (varName in CONTEXT_FIELD_SCHEMAS) {
120
+ properties[varName] = CONTEXT_FIELD_SCHEMAS[varName];
121
+ }
122
+ }
123
+ }
124
+ // Always include 'identifier' as required
125
+ for (const field of ALWAYS_REQUIRED) {
126
+ if (field in properties && !required.includes(field)) {
127
+ required.push(field);
128
+ }
129
+ }
130
+ return {
131
+ $schema: 'http://json-schema.org/draft-07/schema#',
132
+ type: 'object',
133
+ title: `${template.metadata.name} config`,
134
+ description: template.metadata.description ?? `Configuration schema for ${template.metadata.name} template`,
135
+ properties,
136
+ required: required.length > 0 ? required : undefined,
137
+ additionalProperties: true,
138
+ };
139
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=schema.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.test.d.ts","sourceRoot":"","sources":["../../../src/templates/schema.test.ts"],"names":[],"mappings":""}