Skip to content

Commit b81975a

Browse files
committed
feat(workflows): add track support for conditional chained prompts
add selectedTrack option to filter chained prompts by track update related interfaces and functions to handle track filtering
1 parent 203f8ec commit b81975a

6 files changed

Lines changed: 53 additions & 8 deletions

File tree

src/agents/runner/chained.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,17 @@ function meetsConditions(entry: ChainedPathEntry, selectedConditions: string[]):
217217
return entry.conditions.every(c => selectedConditions.includes(c));
218218
}
219219

220+
/**
221+
* Check if track requirement is met
222+
* If entry has tracks specified, selectedTrack must be one of them
223+
*/
224+
function meetsTracks(entry: ChainedPathEntry, selectedTrack: string | null): boolean {
225+
if (typeof entry === 'string') return true;
226+
if (!entry.tracks?.length) return true;
227+
if (!selectedTrack) return false; // Has track requirement but no track selected
228+
return entry.tracks.includes(selectedTrack);
229+
}
230+
220231
/**
221232
* Extract path string from entry
222233
*/
@@ -232,24 +243,34 @@ function getPath(entry: ChainedPathEntry): string {
232243
* @param chainedPromptsPath - Path or array of paths to file(s) or folder(s) containing .md files
233244
* @param projectRoot - Project root for resolving relative paths
234245
* @param selectedConditions - User-selected conditions for filtering conditional paths
246+
* @param selectedTrack - User-selected track for filtering track-specific paths
235247
* @returns Array of ChainedPrompt objects sorted by filename within each folder
236248
*/
237249
export async function loadChainedPrompts(
238250
chainedPromptsPath: ChainedPathEntry | ChainedPathEntry[],
239251
projectRoot: string,
240-
selectedConditions: string[] = []
252+
selectedConditions: string[] = [],
253+
selectedTrack: string | null = null
241254
): Promise<ChainedPrompt[]> {
242255
const entries = Array.isArray(chainedPromptsPath) ? chainedPromptsPath : [chainedPromptsPath];
243256
const allPrompts: ChainedPrompt[] = [];
244257

245258
for (const entry of entries) {
259+
// Check both conditions AND tracks (both must pass)
246260
if (!meetsConditions(entry, selectedConditions)) {
247261
const pathStr = getPath(entry);
248262
const conditions = isConditionalPath(entry) ? entry.conditions : [];
249263
debug(`Skipped chained path: ${pathStr} (unmet conditions: ${conditions?.join(', ')})`);
250264
continue;
251265
}
252266

267+
if (!meetsTracks(entry, selectedTrack)) {
268+
const pathStr = getPath(entry);
269+
const tracks = isConditionalPath(entry) ? entry.tracks : [];
270+
debug(`Skipped chained path: ${pathStr} (unmet tracks: ${tracks?.join(', ')}, selected: ${selectedTrack})`);
271+
continue;
272+
}
273+
253274
const inputPath = getPath(entry);
254275
const prompts = await loadPromptsFromPath(inputPath, projectRoot);
255276
allPrompts.push(...prompts);

src/agents/runner/runner.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ export interface ExecuteAgentOptions {
144144
*/
145145
selectedConditions?: string[];
146146

147+
/**
148+
* Selected track for filtering track-specific chained prompt paths
149+
*/
150+
selectedTrack?: string;
151+
147152
/**
148153
* Skip writing to agent's log file (caller handles logging externally)
149154
*/
@@ -208,7 +213,7 @@ export async function executeAgent(
208213
prompt: string,
209214
options: ExecuteAgentOptions,
210215
): Promise<AgentExecutionOutput> {
211-
const { workingDir, projectRoot, engine: engineOverride, model: modelOverride, logger, stderrLogger, onTelemetry, abortSignal, timeout, parentId, disableMonitoring, ui, uniqueAgentId, displayPrompt, resumeMonitoringId, resumePrompt, resumeSessionId: resumeSessionIdOption, selectedConditions } = options;
216+
const { workingDir, projectRoot, engine: engineOverride, model: modelOverride, logger, stderrLogger, onTelemetry, abortSignal, timeout, parentId, disableMonitoring, ui, uniqueAgentId, displayPrompt, resumeMonitoringId, resumePrompt, resumeSessionId: resumeSessionIdOption, selectedConditions, selectedTrack } = options;
212217

213218
debug(`[AgentRunner] executeAgent called: agentId=%s promptLength=%d`, agentId, prompt.length);
214219
debug(`[AgentRunner] Options: workingDir=%s engineOverride=%s modelOverride=%s parentId=%s`,
@@ -469,7 +474,8 @@ export async function executeAgent(
469474
chainedPrompts = await loadChainedPrompts(
470475
agentConfig.chainedPromptsPath,
471476
projectRoot ?? workingDir,
472-
selectedConditions ?? []
477+
selectedConditions ?? [],
478+
selectedTrack ?? null
473479
);
474480
debug(`[ChainedPrompts] Loaded ${chainedPrompts.length} chained prompts for agent '${agentId}'`);
475481
} else {

src/shared/agents/config/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
export type ConditionalChainedPath = {
55
path: string;
66
conditions?: string[];
7+
tracks?: string[];
78
};
89

910
/**

src/workflows/recovery/restore.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { debug } from '../../shared/logging/logger.js';
1212
import { getNextChainIndex } from '../indexing/lifecycle.js';
1313
import { loadAgentConfig } from '../../agents/runner/index.js';
1414
import { loadChainedPrompts } from '../../agents/runner/chained.js';
15-
import { getSelectedConditions } from '../../shared/workflows/template.js';
15+
import { getSelectedConditions, getSelectedTrack } from '../../shared/workflows/template.js';
1616
import { StatusService } from '../../agents/monitoring/index.js';
1717
import type { CrashRestoreContext, CrashRestoreResult } from './types.js';
1818

@@ -70,10 +70,12 @@ export async function restoreFromCrash(ctx: CrashRestoreContext): Promise<CrashR
7070

7171
if (agentConfig?.chainedPromptsPath) {
7272
const selectedConditions = await getSelectedConditions(cmRoot);
73+
const selectedTrack = await getSelectedTrack(cmRoot);
7374
const chainedPrompts = await loadChainedPrompts(
7475
agentConfig.chainedPromptsPath,
7576
cwd,
76-
selectedConditions
77+
selectedConditions,
78+
selectedTrack
7779
);
7880

7981
if (chainedPrompts.length > 0) {

src/workflows/step/execute.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export interface StepExecutorOptions {
4646
resumeSessionId?: string;
4747
/** Selected conditions for filtering conditional chained prompt paths */
4848
selectedConditions?: string[];
49+
/** Selected track for filtering track-specific chained prompt paths */
50+
selectedTrack?: string;
4951
/** Callback when monitoring ID is registered (for early access before execution completes) */
5052
onMonitoringRegistered?: (monitoringId: number) => void;
5153
}
@@ -144,6 +146,7 @@ export async function executeStep(
144146
resumePrompt: options.resumePrompt,
145147
resumeSessionId: options.resumeSessionId,
146148
selectedConditions: options.selectedConditions,
149+
selectedTrack: options.selectedTrack,
147150
// Pass emitter as UI so runner can register monitoring ID immediately
148151
ui: options.emitter,
149152
// Telemetry auto-forwarding

src/workflows/step/run.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { getUniqueAgentId } from '../context/index.js';
1212
import { executeStep } from './execute.js';
1313
import { selectEngine } from './engine.js';
1414
import { registry } from '../../infra/engines/index.js';
15-
import { getSelectedConditions } from '../../shared/workflows/template.js';
15+
import { getSelectedConditions, getSelectedTrack } from '../../shared/workflows/template.js';
1616
import { loadAgentConfig } from '../../agents/runner/index.js';
1717
import { loadChainedPrompts } from '../../agents/runner/chained.js';
1818
import { beforeRun, afterRun, cleanupRun } from './hooks.js';
@@ -115,14 +115,18 @@ export async function runStepFresh(ctx: RunnerContext): Promise<RunStepResult |
115115
ctx.emitter.updateAgentModel(uniqueAgentId, resolvedModel);
116116
}
117117

118+
// Fetch conditions and track for chained prompt filtering
119+
const selectedConditions = await getSelectedConditions(ctx.cmRoot);
120+
const selectedTrack = await getSelectedTrack(ctx.cmRoot);
121+
118122
// Pre-load chained prompts from agent config so UI can show step info immediately
119123
const agentConfig = await loadAgentConfig(step.agentId, ctx.cwd);
120124
if (agentConfig?.chainedPromptsPath) {
121-
const selectedConditions = await getSelectedConditions(ctx.cmRoot);
122125
const preloadedPrompts = await loadChainedPrompts(
123126
agentConfig.chainedPromptsPath,
124127
ctx.cwd,
125-
selectedConditions
128+
selectedConditions,
129+
selectedTrack
126130
);
127131
if (preloadedPrompts.length > 0) {
128132
debug('[step/run] Pre-loaded %d chained prompts for UI', preloadedPrompts.length);
@@ -150,6 +154,8 @@ export async function runStepFresh(ctx: RunnerContext): Promise<RunStepResult |
150154
emitter: ctx.emitter,
151155
abortSignal: abortController.signal,
152156
uniqueAgentId,
157+
selectedConditions,
158+
selectedTrack: selectedTrack ?? undefined,
153159
});
154160

155161
debug('[step/run] Step completed');
@@ -289,6 +295,10 @@ export async function runStepResume(
289295
ctx.machine.send({ type: 'RESUME' });
290296
ctx.emitter.setWorkflowStatus('running');
291297

298+
// Fetch conditions and track for chained prompt filtering
299+
const selectedConditions = await getSelectedConditions(ctx.cmRoot);
300+
const selectedTrack = await getSelectedTrack(ctx.cmRoot);
301+
292302
try {
293303
const output = await executeStep(step, ctx.cwd, {
294304
logger: () => {},
@@ -299,6 +309,8 @@ export async function runStepResume(
299309
resumeMonitoringId: options.resumeMonitoringId,
300310
resumeSessionId: sessionId,
301311
resumePrompt: options.resumePrompt,
312+
selectedConditions,
313+
selectedTrack: selectedTrack ?? undefined,
302314
});
303315

304316
// Update context with new output

0 commit comments

Comments
 (0)