Break work

This commit is contained in:
2026-01-20 09:38:07 -05:00
parent d83c02759a
commit 4fbd3be324
36 changed files with 3117 additions and 2770 deletions

View File

@@ -64,6 +64,7 @@ export interface CompiledExecutionAction {
parameterSchemaRaw?: unknown;
timeout?: number;
retryable?: boolean;
children?: CompiledExecutionAction[];
}
/* ---------- Compile Entry Point ---------- */
@@ -136,11 +137,12 @@ function compileAction(
robotId: action.source.robotId,
baseActionId: action.source.baseActionId,
},
execution: action.execution,
execution: action.execution!, // Assumes validation passed
parameters: action.parameters,
parameterSchemaRaw: action.parameterSchemaRaw,
timeout: action.execution.timeoutMs,
retryable: action.execution.retryable,
timeout: action.execution?.timeoutMs,
retryable: action.execution?.retryable,
children: action.children?.map((child, i) => compileAction(child, i)),
};
}
@@ -149,17 +151,24 @@ function compileAction(
export function collectPluginDependencies(design: ExperimentDesign): string[] {
const set = new Set<string>();
for (const step of design.steps) {
for (const action of step.actions) {
if (action.source.kind === "plugin" && action.source.pluginId) {
const versionPart = action.source.pluginVersion
? `@${action.source.pluginVersion}`
: "";
set.add(`${action.source.pluginId}${versionPart}`);
}
}
collectDependenciesFromActions(step.actions, set);
}
return Array.from(set).sort();
}
// Helper to recursively collect from actions list directly would be cleaner
function collectDependenciesFromActions(actions: ExperimentAction[], set: Set<string>) {
for (const action of actions) {
if (action.source.kind === "plugin" && action.source.pluginId) {
const versionPart = action.source.pluginVersion
? `@${action.source.pluginVersion}`
: "";
set.add(`${action.source.pluginId}${versionPart}`);
}
if (action.children) {
collectDependenciesFromActions(action.children, set);
}
}
}
/* ---------- Integrity Hash Generation ---------- */
@@ -199,6 +208,12 @@ function buildStructuralSignature(
timeout: a.timeout,
retryable: a.retryable ?? false,
parameterKeys: summarizeParametersForHash(a.parameters),
children: a.children?.map(c => ({
id: c.id,
// Recurse structural signature for children
type: c.type,
parameterKeys: summarizeParametersForHash(c.parameters),
})),
})),
})),
pluginDependencies,

View File

@@ -53,15 +53,18 @@ export interface ActionDefinition {
};
execution?: ExecutionDescriptor;
parameterSchemaRaw?: unknown; // snapshot of original schema for validation/audit
nestable?: boolean; // If true, this action can contain child actions
}
export interface ExperimentAction {
id: string;
type: ActionType;
type: string; // e.g. "wizard_speak", "robot_move"
name: string;
description?: string; // Optional description
parameters: Record<string, unknown>;
duration?: number;
duration?: number; // Estimated duration in seconds
category: ActionCategory;
// Provenance (where did this come from?)
source: {
kind: "core" | "plugin";
pluginId?: string;
@@ -69,8 +72,14 @@ export interface ExperimentAction {
robotId?: string | null;
baseActionId?: string;
};
execution: ExecutionDescriptor;
// Execution (how do we run this?)
execution?: ExecutionDescriptor;
// Snapshot of parameter schema at the time of addition (for drift detection)
parameterSchemaRaw?: unknown;
// Nested actions (control flow)
children?: ExperimentAction[];
}
export interface StepTrigger {

View File

@@ -90,17 +90,26 @@ const executionDescriptorSchema = z
// Action parameter snapshot is a free-form structure retained for audit
const parameterSchemaRawSchema = z.unknown().optional();
// Action schema (loose input → normalized internal)
const visualActionInputSchema = z
.object({
id: z.string().min(1),
type: z.string().min(1),
name: z.string().min(1),
category: actionCategoryEnum.optional(),
parameters: z.record(z.string(), z.unknown()).default({}),
source: actionSourceSchema.optional(),
execution: executionDescriptorSchema.optional(),
parameterSchemaRaw: parameterSchemaRawSchema,
// Base action schema (without recursion)
const baseActionSchema = z.object({
id: z.string().min(1),
type: z.string().min(1),
name: z.string().min(1),
description: z.string().optional(),
category: actionCategoryEnum.optional(),
parameters: z.record(z.string(), z.unknown()).default({}),
source: actionSourceSchema.optional(),
execution: executionDescriptorSchema.optional(),
parameterSchemaRaw: parameterSchemaRawSchema,
});
type VisualActionInput = z.infer<typeof baseActionSchema> & {
children?: VisualActionInput[];
};
const visualActionInputSchema: z.ZodType<VisualActionInput> = baseActionSchema
.extend({
children: z.lazy(() => z.array(visualActionInputSchema)).optional(),
})
.strict();
@@ -144,8 +153,7 @@ export function parseVisualDesignSteps(raw: unknown): {
issues.push(
...zodErr.issues.map(
(issue) =>
`steps${
issue.path.length ? "." + issue.path.join(".") : ""
`steps${issue.path.length ? "." + issue.path.join(".") : ""
}: ${issue.message} (code=${issue.code})`,
),
);
@@ -155,69 +163,73 @@ export function parseVisualDesignSteps(raw: unknown): {
// Normalize to internal ExperimentStep[] shape
const inputSteps = parsed.data;
const normalized: ExperimentStep[] = inputSteps.map((s, idx) => {
const actions: ExperimentAction[] = s.actions.map((a) => {
// Default provenance
const source: {
kind: "core" | "plugin";
pluginId?: string;
pluginVersion?: string;
robotId?: string | null;
baseActionId?: string;
} = a.source
const normalizeAction = (a: VisualActionInput): ExperimentAction => {
// Default provenance
const source: {
kind: "core" | "plugin";
pluginId?: string;
pluginVersion?: string;
robotId?: string | null;
baseActionId?: string;
} = a.source
? {
kind: a.source.kind,
pluginId: a.source.pluginId,
pluginVersion: a.source.pluginVersion,
robotId: a.source.robotId ?? null,
baseActionId: a.source.baseActionId,
}
kind: a.source.kind,
pluginId: a.source.pluginId,
pluginVersion: a.source.pluginVersion,
robotId: a.source.robotId ?? null,
baseActionId: a.source.baseActionId,
}
: { kind: "core" };
// Default execution
const execution: ExecutionDescriptor = a.execution
? {
transport: a.execution.transport,
timeoutMs: a.execution.timeoutMs,
retryable: a.execution.retryable,
ros2: a.execution.ros2,
rest: a.execution.rest
? {
method: a.execution.rest.method,
path: a.execution.rest.path,
headers: a.execution.rest.headers
? Object.fromEntries(
Object.entries(a.execution.rest.headers).filter(
(kv): kv is [string, string] =>
typeof kv[1] === "string",
),
)
: undefined,
}
// Default execution
const execution: ExecutionDescriptor = a.execution
? {
transport: a.execution.transport,
timeoutMs: a.execution.timeoutMs,
retryable: a.execution.retryable,
ros2: a.execution.ros2,
rest: a.execution.rest
? {
method: a.execution.rest.method,
path: a.execution.rest.path,
headers: a.execution.rest.headers
? Object.fromEntries(
Object.entries(a.execution.rest.headers).filter(
(kv): kv is [string, string] =>
typeof kv[1] === "string",
),
)
: undefined,
}
: { transport: "internal" };
: undefined,
}
: { transport: "internal" };
return {
id: a.id,
type: a.type, // dynamic (pluginId.actionId)
name: a.name,
parameters: a.parameters ?? {},
duration: undefined,
category: (a.category ?? "wizard") as ActionCategory,
source: {
kind: source.kind,
pluginId: source.kind === "plugin" ? source.pluginId : undefined,
pluginVersion:
source.kind === "plugin" ? source.pluginVersion : undefined,
robotId: source.kind === "plugin" ? (source.robotId ?? null) : null,
baseActionId:
source.kind === "plugin" ? source.baseActionId : undefined,
},
execution,
parameterSchemaRaw: a.parameterSchemaRaw,
};
});
return {
id: a.id,
type: a.type,
name: a.name,
description: a.description,
parameters: a.parameters ?? {},
duration: undefined,
category: (a.category ?? "wizard") as ActionCategory,
source: {
kind: source.kind,
pluginId: source.kind === "plugin" ? source.pluginId : undefined,
pluginVersion:
source.kind === "plugin" ? source.pluginVersion : undefined,
robotId: source.kind === "plugin" ? (source.robotId ?? null) : null,
baseActionId:
source.kind === "plugin" ? source.baseActionId : undefined,
},
execution,
parameterSchemaRaw: a.parameterSchemaRaw,
children: a.children?.map(normalizeAction) ?? [],
};
};
const normalized: ExperimentStep[] = inputSteps.map((s, idx) => {
const actions: ExperimentAction[] = s.actions.map(normalizeAction);
// Construct step
return {