FN-7038: add live automation run output

Add configurable automation tool access with live manual-run streaming for schedules and routines.

- Add an automation tool allowlist model and dashboard selectors for AI prompt steps.
- Stream manual automation and routine run progress, output, step updates, and tool events over SSE.
- Surface live run transcripts in automation/routine cards and document the operator workflow.
- Cover validation, runner callbacks, API streaming, and UI behavior with targeted tests.

Files changed:
 .changeset/fn-7038-automation-tools-live-output.md |   7 +
 docs/dashboard-guide.md                            |   9 +
 packages/core/src/__tests__/automation.test.ts     |  27 +-
 packages/core/src/automation.ts                    |  18 +
 packages/core/src/index.ts                         |   4 +-
 packages/core/src/types.ts                         |   6 +
 packages/dashboard/app/api/legacy.ts               |  48 +++
 packages/dashboard/app/components/RoutineCard.tsx  |  16 +-
 packages/dashboard/app/components/ScheduleForm.tsx |  53 ++-
 .../app/components/ScheduleStepsEditor.tsx         |  49 ++-
 .../app/components/ScheduledTasksModal.tsx         |  72 +++-
 packages/dashboard/app/components/ScriptsModal.css |  79 ++++
 .../app/components/__tests__/RoutineCard.test.tsx  |  45 +++
 .../app/components/__tests__/ScheduleForm.test.tsx | 100 +++++-
 .../__tests__/ScheduleStepsEditor.test.tsx         |  67 +++-
 .../__tests__/ScheduledTasksModal.test.tsx         |  36 +-
 .../src/__tests__/routes-automation.test.ts        | 151 +++++++-
 packages/dashboard/src/routes.ts                   | 400 ++++++++++++++++++++-
 packages/dashboard/src/server.ts                   |   7 +-
 packages/engine/src/__tests__/cron-runner.test.ts  |  56 ++-
 .../src/__tests__/pi-create-fn-agent.test.ts       |  44 +++
 .../engine/src/__tests__/routine-runner.test.ts    |  28 ++
 packages/engine/src/cron-runner.ts                 |  20 +-
 packages/engine/src/pi.ts                          |  32 +-
 packages/engine/src/routine-runner.ts              |  39 +-
 packages/i18n/locales/en/app.json                  |   9 +
 packages/i18n/src/resources.d.ts                   |  21 ++
 27 files changed, 1374 insertions(+), 69 deletions(-)

Fusion-Task-Id: FN-7038

Fusion-Task-Lineage: 7f5871d0-1de0-4d22-a9c9-ee9418658677
This commit is contained in:
gsxdsm
2026-06-26 02:45:29 -07:00
parent 301f25da06
commit 8131d541ca
27 changed files with 1374 additions and 69 deletions

View File

@@ -5142,6 +5142,21 @@ export function reorderAutomationSteps(id: string, stepIds: string[], options?:
export interface RoutineRunResponse {
routine: Routine;
result: RoutineExecutionResult;
liveRunId?: string;
}
export type RoutineRunStreamEvent =
| { type: "run"; runId?: string; scheduleId?: string; status?: string }
| { type: "step"; runId?: string; stepIndex?: number; stepId?: string; stepName?: string; stepType?: string; status?: string; success?: boolean; error?: string }
| { type: "output"; runId?: string; text?: string }
| { type: "tool"; runId?: string; status?: string; name?: string; args?: unknown; isError?: boolean; result?: unknown }
| { type: "complete"; runId?: string; result?: RoutineExecutionResult }
| { type: "error"; runId?: string; message?: string; result?: RoutineExecutionResult };
export interface RoutineRunStreamHandlers {
onEvent: (event: RoutineRunStreamEvent) => void;
onConnectionStateChange?: (state: StreamConnectionState) => void;
onFatalError?: (message: string) => void;
}
export function fetchRoutines(options?: SchedulingScopeOptions): Promise<Routine[]> {
@@ -5180,6 +5195,39 @@ export function runRoutine(id: string, options?: SchedulingScopeOptions): Promis
});
}
export function streamRoutineRun(id: string, handlers: RoutineRunStreamHandlers, options?: SchedulingScopeOptions & { runId?: string }) {
const baseUrl = withSchedulingScope(`/routines/${id}/run/stream`, options);
const separator = baseUrl.includes("?") ? "&" : "?";
const url = options?.runId ? `${baseUrl}${separator}runId=${encodeURIComponent(options.runId)}` : baseUrl;
const parse = (type: RoutineRunStreamEvent["type"], event: MessageEvent) => {
let data: Record<string, unknown> = {};
try {
data = event.data ? JSON.parse(event.data) : {};
} catch {
data = { message: event.data };
}
handlers.onEvent({ type, ...data } as RoutineRunStreamEvent);
};
return createResilientEventSource(
url,
{
events: {
run: (event) => parse("run", event),
step: (event) => parse("step", event),
output: (event) => parse("output", event),
tool: (event) => parse("tool", event),
complete: (event) => parse("complete", event),
error: (event) => parse("error", event),
},
},
{
maxReconnectAttempts: 2,
onConnectionStateChange: handlers.onConnectionStateChange,
onFatalError: handlers.onFatalError,
},
);
}
export function fetchRoutineRuns(id: string, options?: SchedulingScopeOptions): Promise<RoutineExecutionResult[]> {
return api<RoutineExecutionResult[]>(withSchedulingScope(`/routines/${id}/runs`, options));
}