Let the agent use the graph, scene and project commands (#429)
feuerwerk / gate (push) Successful in 5m10s
feuerwerk / suite (push) Failing after 18m49s

The agent knew 23 of the editor's commands. It could not point a
Variables node at a variable, group nodes, edit ports, transitions,
containers, colliders or the project settings. Stage 5a brings the
catalog to 85 commands in five areas and keeps every answer short.

- The catalog lists names and descriptions only. With a query (an
  area or command names) it adds the arguments. All schemas at once
  would pass the 60 000 character limit of one answer.
- add_port points a Variables node at a variable. The port takes the
  variable's kind, and an unknown name is refused with the list of
  the ones that exist.
- add_group and add_comment take nodes instead of coordinates. They
  follow the nodes after the arrange step, in the same undo step.
- The rule for which list a port picks from moved into portChoices.ts.
  Inspector and agent now share it.
- The frame of a group around nodes moved into annotationDrag.ts, so
  the button and the agent get the same frame.
- list_assets returns the project settings. get_asset shows the
  content of placed containers with the ids overrides need. get_graph
  shows declared ports and frame colors.
- A new node is placed at the height of its port, in the free spot
  whose links cross the fewest others. A source goes left of the node
  it feeds. Before, new nodes always went right of the rest.
- Arrange breaks a tie in crossings by putting the flow on top, and
  exec links pull harder when a column is sorted.
- A comment over a group keeps clear of the group's title bar.
- The agent's instructions and lint_graph ask to look at crossings
  too and to call arrange_graph when there are more than needed.

Left out on purpose: file assets (no upload), paste from the
clipboard, internal wrapper commands and switching plugins on.

Found on the way:
- Variable commands on a Struct asset did not run through
  WithStructTypes. New fields did not reach variables of that type.
- A query "variables" also matched set_variables_scope. A word that
  names an area now means only that area.

Tests:
- agent-catalog (new, 10), agent-edit, graph-arrange (3 new) and
  relay green. 11 bends of the code, all red.
- e2e 71 on atx: 22/22, the new check sees port speed and a group
  around all 4 drawn nodes.
- Live on a test project: a speed variable of 300 moved the player
  to x 150.03 in 0.5 s. A re-placed Variables node landed below Axis
  input with 4 crossings instead of 7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-18 16:01:15 +02:00
1 parent 1943539a7a
commit 0d8fd6ff52
26 files changed
+2585 -672

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
import type { Command } from '../../../core/commands/Command';
import type { AnnotationKind } from '../../../core/graph';
import type { Project } from '../../../core/model/Project';
import type { MeasureNode } from '../../graph/graphLayout';
import type { ArgSchema } from '../argCheck';
/** What a command is built from: the project as it stands before the edit, and where new nodes go. */
export interface BuildContext {
project: Project;
/** A free place for a node the agent gave none, right of what the graph and this edit hold. */
spot(assetId: string): { x: number; y: number };
/** How the nodes of a graph are measured for arranging. */
measure(assetId: string): MeasureNode;
}
/** The command, and the ids of what it makes – what later steps of the same edit can name. */
export interface Built {
command: Command;
/** A function for what is only known once the command has run. */
made?: Record<string, string> | (() => Record<string, string>);
/** Nodes it added without a place: they are arranged when the edit's steps have run. */
unplaced?: { asset: string; nodes: string[] };
/** A group or comment it put at nodes: fitted to them again once the edit has arranged its nodes. */
frames?: Frame;
}
export interface Frame {
asset: string;
kind: AnnotationKind;
id: string;
nodes: readonly string[];
}
export type Args = Record<string, unknown>;
/** What a step worked on, for the editor to show the user: an asset, objects of a holder, nodes of a graph. */
export type StepTarget =
| { kind: 'asset'; asset: string }
| { kind: 'objects'; in: string; ids: string[] }
| { kind: 'nodes'; asset: string; ids: string[] };
/** One command the agent can run (#429): what it does, its arguments, and how it is made. */
export interface CatalogCommand {
name: string;
description: string;
args: ArgSchema;
/** Deletes something the user has to agree to first – decision 4 of #429. */
confirms?: true;
build(args: Args, context: BuildContext): Built;
/** What it worked on, from its arguments and what it made; none for a step there is nothing to show of. */
shows?(args: Args, made: Record<string, string>): StepTarget;
}
export const id = (description: string): ArgSchema => ({ type: 'string', minLength: 1, description });
export const number = (description: string): ArgSchema => ({ type: 'number', description });
export const flag = (description: string): ArgSchema => ({ type: 'boolean', description });
export const ids = (description: string): ArgSchema => ({
type: 'array',
items: { type: 'string', minLength: 1 },
minItems: 1,
description,
});
export const oneOf = (values: readonly string[], description: string): ArgSchema => ({
type: 'string',
enum: values,
description,
});
export const anyValue: ArgSchema = {
description: 'Any JSON value; it is checked against the kind of the property.',
};
export const object = (properties: Record<string, ArgSchema>, required: string[]): ArgSchema => ({
type: 'object',
properties,
required,
additionalProperties: false,
});
export const ASSET = id('Id of the asset.');
export const HOLDER = id('Id of the scene or container the object is in.');
export const OBJECT = id('Id of the placed object.');
export const GRAPH = id('Id of the asset holding the graph (behavior, state machine, shader, ...).');
export const NODE = id('Id of the node.');
export const PATH: ArgSchema = {
type: 'array',
items: { type: 'string', minLength: 1 },
description:
'Field keys leading into a struct variable, outermost first; leave out for the variable itself.',
};
export const text = (args: Args, key: string): string => args[key] as string;
export const numberOf = (args: Args, key: string): number => args[key] as number;
export const listOf = (args: Args, key: string): string[] => args[key] as string[];
export const theAsset = (args: Args): StepTarget => ({ kind: 'asset', asset: text(args, 'asset') });
export const madeAsset = (_args: Args, made: Record<string, string>): StepTarget => ({
kind: 'asset',
asset: made.id!,
});
export const theObject = (args: Args): StepTarget => ({
kind: 'objects',
in: text(args, 'in'),
ids: [text(args, 'object')],
});
export const theObjects = (args: Args): StepTarget => ({
kind: 'objects',
in: text(args, 'in'),
ids: listOf(args, 'objects'),
});
export const theNode = (args: Args): StepTarget => ({
kind: 'nodes',
asset: text(args, 'asset'),
ids: [text(args, 'node')],
});
export const theGraph = (args: Args): StepTarget => ({ kind: 'nodes', asset: text(args, 'asset'), ids: [] });