feat(dashboard): success/failure edge authoring with cycle guard and interpreter-only banner
This commit is contained in:
@@ -95,6 +95,10 @@ export function validateLinearity(ir: WorkflowIr): WorkflowCompileError | null {
|
||||
return new WorkflowCompileError(`node '${node.id}' has no outgoing edge`);
|
||||
}
|
||||
if (outs.length > 1) {
|
||||
// NOTE: the `require the workflow interpreter (deferred)` suffix is matched
|
||||
// by the dashboard editor (WorkflowNodeEditor handleSave, KTD-4) to render
|
||||
// an info-tone "interpreter-only" banner instead of an error. Keep both
|
||||
// interpreter-deferred messages carrying this exact suffix in sync.
|
||||
return new WorkflowCompileError(
|
||||
`node '${node.id}' branches into ${outs.length} edges — graphs with branches require the workflow interpreter (deferred)`,
|
||||
);
|
||||
|
||||
@@ -202,6 +202,14 @@
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Info-tone banner (KTD-4): branching graph runs on the interpreter only — not a
|
||||
* failure, so it uses the info token rather than the warning treatment. */
|
||||
.wf-editor-banner--info {
|
||||
border-bottom-color: var(--ws-info);
|
||||
color: var(--ws-info);
|
||||
background: color-mix(in srgb, var(--ws-info) 6%, var(--bg-secondary));
|
||||
}
|
||||
|
||||
.wf-editor-canvas {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -418,6 +426,16 @@
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
/* Failure edges (R2): a distinct dash pattern from rework plus an error-token
|
||||
* stroke. Two-channel rule — the condition label is always rendered (third
|
||||
* channel is color) so failure edges stay distinguishable in low-contrast
|
||||
* themes. */
|
||||
.react-flow__edge.wf-edge-failure .react-flow__edge-path {
|
||||
stroke: var(--ws-error);
|
||||
stroke-dasharray: 2 4;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.wf-code-source {
|
||||
font-family: var(--font-mono, monospace);
|
||||
font-size: 0.72rem;
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Background,
|
||||
Controls,
|
||||
MiniMap,
|
||||
addEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
type Connection,
|
||||
@@ -51,6 +50,10 @@ import {
|
||||
isColumnBandNode,
|
||||
foreachChildFlowId,
|
||||
shortConditionLabel,
|
||||
edgeClassName,
|
||||
edgeConditionEditability,
|
||||
buildConnectionEdge,
|
||||
WF_EDGE_INTERACTION_WIDTH,
|
||||
FOREACH_GROUP_WIDTH,
|
||||
FOREACH_GROUP_HEIGHT,
|
||||
FOREACH_CHILD_X,
|
||||
@@ -131,6 +134,10 @@ function InnerEditor({
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [validationError, setValidationError] = useState<string | null>(null);
|
||||
// Info-tone state (KTD-4): set when a save compiles-rejects solely because the
|
||||
// graph branches (interpreter-only), distinct from the warning-toned
|
||||
// validationError used for genuine problems.
|
||||
const [interpreterOnly, setInterpreterOnly] = useState<boolean>(false);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState<FlowNode<WorkflowFlowNodeData>>([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState<FlowEdge>([]);
|
||||
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
|
||||
@@ -225,6 +232,7 @@ function InnerEditor({
|
||||
setSelectedNodeId(null);
|
||||
setSelectedEdgeId(null);
|
||||
setValidationError(null);
|
||||
setInterpreterOnly(false);
|
||||
}, [activeWorkflow, setNodes, setEdges]);
|
||||
|
||||
// Server-reported node error (e.g. seam-in-branch) attributed to a node id.
|
||||
@@ -240,13 +248,28 @@ function InnerEditor({
|
||||
});
|
||||
}, [columns, setNodes]);
|
||||
|
||||
// Append a new (success) edge directly rather than via React Flow's addEdge,
|
||||
// which dedupes on source/target/handles and would block parallel
|
||||
// success+failure edges between the same pair (KTD-3). buildConnectionEdge
|
||||
// reimplements addEdge's sanity guards plus the author-time cycle guard (KTD-9).
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
setEdges((eds) =>
|
||||
addEdge({ ...connection, label: "success", data: { condition: "success" } }, eds),
|
||||
);
|
||||
const result = buildConnectionEdge(connection, edges, nodes);
|
||||
if ("error" in result) {
|
||||
if (result.error === "cycle") {
|
||||
addToast(
|
||||
t(
|
||||
"workflowNodes.cycleBlocked",
|
||||
"That connection would create a cycle — only rework edges inside a for-each template may loop back",
|
||||
),
|
||||
"warning",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
setEdges((eds) => [...eds, result.edge]);
|
||||
},
|
||||
[setEdges],
|
||||
[edges, nodes, setEdges, addToast, t],
|
||||
);
|
||||
|
||||
// Dragging a step node into a column band sets node.column (position-based
|
||||
@@ -372,7 +395,7 @@ function InnerEditor({
|
||||
data: { ...(e.data ?? {}), condition, kind: rework ? "rework" : undefined },
|
||||
type: rework ? "step" : undefined,
|
||||
animated: rework,
|
||||
className: rework ? "wf-edge-rework" : undefined,
|
||||
className: edgeClassName(condition, rework),
|
||||
};
|
||||
}),
|
||||
);
|
||||
@@ -459,6 +482,7 @@ function InnerEditor({
|
||||
|
||||
setSaving(true);
|
||||
setValidationError(null);
|
||||
setInterpreterOnly(false);
|
||||
setServerNodeError(null);
|
||||
try {
|
||||
const { ir, layout } = flowToIr(
|
||||
@@ -475,9 +499,19 @@ function InnerEditor({
|
||||
await compileWorkflow(updated.id, projectId);
|
||||
addToast(t("workflows.saved", "Workflow saved"), "success");
|
||||
} catch (compileErr) {
|
||||
setValidationError(
|
||||
getErrorMessage(compileErr) || t("workflows.savedNotCompilable", "Workflow saved but cannot be compiled"),
|
||||
);
|
||||
const compileMsg = getErrorMessage(compileErr) || "";
|
||||
// KTD-4: branching graphs reject with this shared suffix from
|
||||
// workflow-compiler.ts (both the fan-out and off-main-path messages).
|
||||
// Such a graph still runs on the interpreter — present it as info, not a
|
||||
// warning. NOTE: this string is coupled to the compiler's message; if
|
||||
// that wording changes, update both sites (see compiler message site).
|
||||
if (compileMsg.includes("require the workflow interpreter (deferred)")) {
|
||||
setInterpreterOnly(true);
|
||||
} else {
|
||||
setValidationError(
|
||||
compileMsg || t("workflows.savedNotCompilable", "Workflow saved but cannot be compiled"),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
const message = getErrorMessage(err) || t("workflows.saveFailed", "Failed to save workflow");
|
||||
@@ -530,12 +564,13 @@ function InnerEditor({
|
||||
|
||||
const selectedNode = nodes.find((n) => n.id === selectedNodeId) ?? null;
|
||||
const selectedEdge = edges.find((e) => e.id === selectedEdgeId) ?? null;
|
||||
// The edge inspector's verdict/rework controls apply only when the edge's
|
||||
// source node is a step-review node (KTD-4).
|
||||
const selectedEdgeSourceIsReview = useMemo(() => {
|
||||
if (!selectedEdge) return false;
|
||||
// The edge inspector renders different controls per source-node kind (KTD-2):
|
||||
// step-review → verdict controls; prompt/script/gate/code/foreach →
|
||||
// success/failure select; everything else → a read-only condition note.
|
||||
const selectedEdgeEditability = useMemo(() => {
|
||||
if (!selectedEdge) return "readonly" as const;
|
||||
const src = nodes.find((n) => n.id === selectedEdge.source);
|
||||
return src?.data.kind === "step-review";
|
||||
return edgeConditionEditability(src?.data.kind);
|
||||
}, [selectedEdge, nodes]);
|
||||
|
||||
// Artifacts the active workflow declares (KTD-12). The parse-steps inspector
|
||||
@@ -711,6 +746,18 @@ function InnerEditor({
|
||||
{validationError}
|
||||
</div>
|
||||
)}
|
||||
{interpreterOnly && (
|
||||
<div
|
||||
className="wf-editor-banner wf-editor-banner--info"
|
||||
role="status"
|
||||
data-testid="wf-interpreter-only-banner"
|
||||
>
|
||||
{t(
|
||||
"workflowNodes.interpreterOnly",
|
||||
"This workflow branches, so it runs on the graph interpreter — it can't compile to the linear step engine, but it will still run.",
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{unplaced.length > 0 && (
|
||||
<div className="wf-editor-banner wf-editor-banner--warn" role="alert" data-testid="wf-unplaced-summary">
|
||||
{t("workflowColumns.unplacedCount", "{{count}} nodes not placed in a column", {
|
||||
@@ -741,6 +788,7 @@ function InnerEditor({
|
||||
setSelectedNodeId(null);
|
||||
setSelectedEdgeId(null);
|
||||
}}
|
||||
defaultEdgeOptions={{ interactionWidth: WF_EDGE_INTERACTION_WIDTH }}
|
||||
fitView
|
||||
>
|
||||
<Background />
|
||||
@@ -1350,7 +1398,7 @@ function InnerEditor({
|
||||
<aside className="wf-editor-inspector" data-testid="wf-edge-inspector">
|
||||
<h3>{t("workflowNodes.edgeInspector", "Edge")}</h3>
|
||||
<fieldset className="wf-inspector-fields" disabled={isBuiltin}>
|
||||
{selectedEdgeSourceIsReview ? (
|
||||
{selectedEdgeEditability === "verdicts" ? (
|
||||
<>
|
||||
<label className="wf-field">
|
||||
<span>{t("workflowNodes.edgeVerdict", "Review verdict")}</span>
|
||||
@@ -1389,6 +1437,18 @@ function InnerEditor({
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
) : selectedEdgeEditability === "conditions" ? (
|
||||
<label className="wf-field">
|
||||
<span>{t("workflowNodes.edgeCondition", "Condition")}</span>
|
||||
<select
|
||||
data-testid="wf-edge-condition"
|
||||
value={String(selectedEdge.data?.condition ?? "success")}
|
||||
onChange={(e) => updateSelectedEdge({ condition: e.target.value })}
|
||||
>
|
||||
<option value="success">success</option>
|
||||
<option value="failure">failure</option>
|
||||
</select>
|
||||
</label>
|
||||
) : (
|
||||
<p className="wf-inspector-note">
|
||||
{t(
|
||||
|
||||
@@ -652,3 +652,56 @@ describe("WorkflowNodeEditor — built-in stepwise selection render path", () =>
|
||||
expect(approve?.kind).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── U2: edge-condition authoring (compile-banner split) ─────────────────────
|
||||
describe("WorkflowNodeEditor — U2 interpreter-only banner", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(fetchTraits).mockResolvedValue(TRAIT_CATALOG);
|
||||
vi.mocked(fetchStepParsers).mockResolvedValue(["step-headings", "json-steps"]);
|
||||
vi.mocked(updateWorkflow).mockImplementation(async (_id, updates) => ({
|
||||
...v2Def(),
|
||||
...(updates as object),
|
||||
}));
|
||||
});
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
async function saveActive() {
|
||||
await screen.findByText("Save");
|
||||
await waitFor(() => expect(screen.getAllByLabelText(/Column name/i).length).toBeGreaterThan(0));
|
||||
fireEvent.click(screen.getByText("Save").closest("button")!);
|
||||
await waitFor(() => expect(updateWorkflow).toHaveBeenCalled());
|
||||
}
|
||||
|
||||
it("shows an info-tone status banner (not an error) when compile rejects with the interpreter-deferred suffix", async () => {
|
||||
vi.mocked(fetchWorkflows).mockResolvedValue([v2Def()]);
|
||||
vi.mocked(compileWorkflow).mockRejectedValue(
|
||||
new Error(
|
||||
"node 'step' branches into 2 edges — graphs with branches require the workflow interpreter (deferred)",
|
||||
),
|
||||
);
|
||||
|
||||
render(<WorkflowNodeEditor isOpen onClose={() => {}} addToast={() => {}} />);
|
||||
await saveActive();
|
||||
|
||||
const banner = await screen.findByTestId("wf-interpreter-only-banner");
|
||||
expect(banner).toHaveAttribute("role", "status");
|
||||
expect(banner.className).toMatch(/wf-editor-banner--info/);
|
||||
// No alert-toned error banner.
|
||||
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the warning error banner for other (non-interpreter) compile errors", async () => {
|
||||
vi.mocked(fetchWorkflows).mockResolvedValue([v2Def()]);
|
||||
vi.mocked(compileWorkflow).mockRejectedValue(new Error("node 'step' has no outgoing edge"));
|
||||
|
||||
render(<WorkflowNodeEditor isOpen onClose={() => {}} addToast={() => {}} />);
|
||||
await saveActive();
|
||||
|
||||
const banner = await screen.findByRole("alert");
|
||||
expect(banner).toHaveTextContent(/no outgoing edge/i);
|
||||
expect(screen.queryByTestId("wf-interpreter-only-banner")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,10 @@ import {
|
||||
foreachChildFlowId,
|
||||
templateNodeIdFromChild,
|
||||
shortConditionLabel,
|
||||
edgeClassName,
|
||||
edgeConditionEditability,
|
||||
wouldCreateCycle,
|
||||
buildConnectionEdge,
|
||||
COLUMN_BAND_HEIGHT,
|
||||
WF_CARD_WIDTH,
|
||||
WF_CARD_MAX_WIDTH,
|
||||
@@ -457,3 +461,143 @@ describe("card dimension constants (U1)", () => {
|
||||
expect(FOREACH_CHILD_Y + WF_CARD_HEIGHT).toBeLessThanOrEqual(FOREACH_GROUP_HEIGHT);
|
||||
});
|
||||
});
|
||||
|
||||
describe("edge-condition authoring (U2)", () => {
|
||||
it("round-trips a failure condition through flowToIr → irToFlow with class + label", () => {
|
||||
const ir: WorkflowDefinition["ir"] = {
|
||||
version: "v1",
|
||||
name: "wf",
|
||||
nodes: [
|
||||
{ id: "start", kind: "start" },
|
||||
{ id: "n1", kind: "prompt", config: { prompt: "do" } },
|
||||
{ id: "ok", kind: "prompt", config: { prompt: "ok" } },
|
||||
{ id: "bad", kind: "prompt", config: { prompt: "bad" } },
|
||||
{ id: "end", kind: "end" },
|
||||
],
|
||||
edges: [
|
||||
{ from: "start", to: "n1", condition: "success" },
|
||||
{ from: "n1", to: "ok", condition: "success" },
|
||||
{ from: "n1", to: "bad", condition: "failure" },
|
||||
{ from: "ok", to: "end", condition: "success" },
|
||||
{ from: "bad", to: "end", condition: "success" },
|
||||
],
|
||||
};
|
||||
const { nodes, edges } = irToFlow(makeDef(ir));
|
||||
const failFlow = edges.find((e) => e.source === "n1" && e.target === "bad")!;
|
||||
expect(failFlow.data?.condition).toBe("failure");
|
||||
expect(failFlow.label).toBe("failure");
|
||||
expect(failFlow.className).toBe("wf-edge-failure");
|
||||
|
||||
const { ir: out } = flowToIr("wf", nodes, edges);
|
||||
const irFail = out.edges.find((e) => e.from === "n1" && e.to === "bad");
|
||||
expect(irFail?.condition).toBe("failure");
|
||||
});
|
||||
|
||||
it("preserves parallel success+failure edges between the same pair through the round-trip", () => {
|
||||
const flowNodes: FlowNode<WorkflowFlowNodeData>[] = [
|
||||
{ id: "a", type: "prompt", position: { x: 0, y: 0 }, data: { kind: "prompt", label: "a" } },
|
||||
{ id: "b", type: "prompt", position: { x: 100, y: 0 }, data: { kind: "prompt", label: "b" } },
|
||||
];
|
||||
const flowEdges = [
|
||||
{ id: "e-1", source: "a", target: "b", data: { condition: "success" } },
|
||||
{ id: "e-2", source: "a", target: "b", data: { condition: "failure" } },
|
||||
];
|
||||
const { ir } = flowToIr("wf", flowNodes, flowEdges);
|
||||
expect(ir.edges).toHaveLength(2);
|
||||
expect(ir.edges.map((e) => e.condition).sort()).toEqual(["failure", "success"]);
|
||||
|
||||
const { edges: reFlow } = irToFlow(makeDef(ir));
|
||||
expect(reFlow).toHaveLength(2);
|
||||
const ids = new Set(reFlow.map((e) => e.id));
|
||||
expect(ids.size).toBe(2);
|
||||
});
|
||||
|
||||
it("edgeClassName: failure → wf-edge-failure, rework precedence, success → undefined", () => {
|
||||
expect(edgeClassName("failure", false)).toBe("wf-edge-failure");
|
||||
expect(edgeClassName("success", false)).toBeUndefined();
|
||||
expect(edgeClassName("failure", true)).toBe("wf-edge-rework");
|
||||
});
|
||||
|
||||
it("edgeConditionEditability gates by source kind (KTD-2)", () => {
|
||||
expect(edgeConditionEditability("step-review")).toBe("verdicts");
|
||||
expect(edgeConditionEditability("prompt")).toBe("conditions");
|
||||
expect(edgeConditionEditability("script")).toBe("conditions");
|
||||
expect(edgeConditionEditability("gate")).toBe("conditions");
|
||||
expect(edgeConditionEditability("code")).toBe("conditions");
|
||||
expect(edgeConditionEditability("foreach")).toBe("conditions");
|
||||
expect(edgeConditionEditability("split")).toBe("readonly");
|
||||
expect(edgeConditionEditability("parse-steps")).toBe("readonly");
|
||||
expect(edgeConditionEditability("start")).toBe("readonly");
|
||||
expect(edgeConditionEditability(undefined)).toBe("readonly");
|
||||
});
|
||||
|
||||
it("wouldCreateCycle detects back-edges and ignores forward + rework edges", () => {
|
||||
const chain = [
|
||||
{ id: "1", source: "a", target: "b", data: { condition: "success" } },
|
||||
{ id: "2", source: "b", target: "c", data: { condition: "success" } },
|
||||
];
|
||||
// c → a closes the loop a→b→c→a.
|
||||
expect(wouldCreateCycle(chain, "c", "a")).toBe(true);
|
||||
// a → c is forward, no cycle.
|
||||
expect(wouldCreateCycle(chain, "a", "c")).toBe(false);
|
||||
// self-loop.
|
||||
expect(wouldCreateCycle(chain, "a", "a")).toBe(true);
|
||||
|
||||
// rework edges are excluded from the reachability walk.
|
||||
const withRework = [
|
||||
{ id: "1", source: "a", target: "b", data: { condition: "success" } },
|
||||
{ id: "2", source: "b", target: "c", data: { condition: "success", kind: "rework" } },
|
||||
];
|
||||
// c only reachable from b via a rework edge, so c→a is NOT a (non-rework) cycle.
|
||||
expect(wouldCreateCycle(withRework, "c", "a")).toBe(false);
|
||||
});
|
||||
|
||||
it("buildConnectionEdge: builds a success edge, rejects missing endpoints, duplicates, cycles", () => {
|
||||
const nodes: FlowNode<WorkflowFlowNodeData>[] = [
|
||||
{ id: "a", type: "prompt", position: { x: 0, y: 0 }, data: { kind: "prompt", label: "a" } },
|
||||
{ id: "b", type: "prompt", position: { x: 100, y: 0 }, data: { kind: "prompt", label: "b" } },
|
||||
{ id: "c", type: "prompt", position: { x: 200, y: 0 }, data: { kind: "prompt", label: "c" } },
|
||||
];
|
||||
const edges = [
|
||||
{ id: "1", source: "a", target: "b", data: { condition: "success" } },
|
||||
{ id: "2", source: "b", target: "c", data: { condition: "success" } },
|
||||
];
|
||||
|
||||
// happy path: new success edge with a unique id + interactionWidth.
|
||||
const ok = buildConnectionEdge({ source: "a", target: "c" }, edges, nodes);
|
||||
expect("edge" in ok).toBe(true);
|
||||
if ("edge" in ok) {
|
||||
expect(ok.edge.data?.condition).toBe("success");
|
||||
expect(ok.edge.label).toBe("success");
|
||||
expect(ok.edge.interactionWidth).toBeGreaterThan(0);
|
||||
expect(typeof ok.edge.id).toBe("string");
|
||||
}
|
||||
|
||||
// missing endpoint.
|
||||
expect(buildConnectionEdge({ source: "a", target: null }, edges, nodes)).toEqual({
|
||||
error: "missing-endpoint",
|
||||
});
|
||||
|
||||
// duplicate of the same condition.
|
||||
expect(buildConnectionEdge({ source: "a", target: "b" }, edges, nodes)).toEqual({
|
||||
error: "duplicate",
|
||||
});
|
||||
|
||||
// cycle: c→a closes a→b→c→a.
|
||||
expect(buildConnectionEdge({ source: "c", target: "a" }, edges, nodes)).toEqual({
|
||||
error: "cycle",
|
||||
});
|
||||
});
|
||||
|
||||
it("buildConnectionEdge exempts intra-foreach-template connections from the cycle guard", () => {
|
||||
const nodes: FlowNode<WorkflowFlowNodeData>[] = [
|
||||
{ id: "g", type: "foreach", position: { x: 0, y: 0 }, data: { kind: "foreach", label: "g" } },
|
||||
{ id: "g::a", type: "prompt", position: { x: 0, y: 0 }, parentId: "g", data: { kind: "prompt", label: "a" } },
|
||||
{ id: "g::b", type: "prompt", position: { x: 0, y: 0 }, parentId: "g", data: { kind: "prompt", label: "b" } },
|
||||
];
|
||||
const edges = [{ id: "1", source: "g::a", target: "g::b", data: { condition: "success" } }];
|
||||
// b→a would be a cycle, but both are children of the same group → allowed.
|
||||
const res = buildConnectionEdge({ source: "g::b", target: "g::a" }, edges, nodes);
|
||||
expect("edge" in res).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -152,8 +152,19 @@ function foreachConfigOf(node: WorkflowIrNode): WorkflowForeachConfig | undefine
|
||||
return cfg as WorkflowForeachConfig;
|
||||
}
|
||||
|
||||
/** CSS class for an edge given its condition + rework kind. Rework takes
|
||||
* precedence; failure edges get the distinct failure styling; success and other
|
||||
* conditions get no class (default styling). R2's two-channel rule (label always
|
||||
* rendered + dash pattern) plus color is enforced by the CSS for these classes. */
|
||||
export function edgeClassName(condition: string, isRework: boolean): string | undefined {
|
||||
if (isRework) return "wf-edge-rework";
|
||||
if (condition === "failure") return "wf-edge-failure";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Build a React Flow edge from an IR edge. Rework edges (KTD-5) carry kind so
|
||||
* the editor renders them dashed in the accent color. */
|
||||
* the editor renders them dashed in the accent color. Failure edges (R2) carry
|
||||
* the wf-edge-failure class for a distinct dash + error-token stroke. */
|
||||
function irEdgeToFlow(edge: WorkflowIrEdge, index: number, idScope = ""): FlowEdge {
|
||||
const condition = edge.condition ?? "success";
|
||||
const isRework = edge.kind === "rework";
|
||||
@@ -165,11 +176,16 @@ function irEdgeToFlow(edge: WorkflowIrEdge, index: number, idScope = ""): FlowEd
|
||||
data: { condition, kind: isRework ? "rework" : undefined },
|
||||
type: isRework ? "step" : undefined,
|
||||
animated: isRework,
|
||||
className: isRework ? "wf-edge-rework" : undefined,
|
||||
className: edgeClassName(condition, isRework),
|
||||
interactionWidth: WF_EDGE_INTERACTION_WIDTH,
|
||||
markerEnd: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/** Default edge hit-target width (px) so edges are clickable/tappable even when
|
||||
* visually thin. Applied per-edge (defaultEdgeOptions only seeds new edges). */
|
||||
export const WF_EDGE_INTERACTION_WIDTH = 24;
|
||||
|
||||
/** Short display label for an edge condition. `outcome:<verdict>` conditions
|
||||
* render as the verdict alone (KTD-4); everything else verbatim. */
|
||||
export function shortConditionLabel(condition: string): string {
|
||||
@@ -399,6 +415,123 @@ function flowEdgeToIr(edge: FlowEdge, groupId?: string): WorkflowIrEdge {
|
||||
return { from, to, condition, ...(isRework ? { kind: "rework" as const } : {}) };
|
||||
}
|
||||
|
||||
// ── Edge-condition authoring (U2) ────────────────────────────────────────────
|
||||
|
||||
/** Editor node kinds whose edges expose a success/failure condition select
|
||||
* (KTD-2). step-review uses verdict controls; all other kinds are read-only. */
|
||||
const CONDITION_EDITABLE_KINDS = new Set<string>(["prompt", "script", "gate", "code", "foreach"]);
|
||||
|
||||
/** Decide what the edge inspector renders for an edge sourced from `sourceKind`:
|
||||
* - "verdicts": step-review verdict select + rework checkbox (existing);
|
||||
* - "conditions": success/failure native select (KTD-2);
|
||||
* - "readonly": a read-only condition note. Pure so the gating is unit-testable
|
||||
* without rendering edges (jsdom can't). */
|
||||
export function edgeConditionEditability(
|
||||
sourceKind: string | undefined,
|
||||
): "verdicts" | "conditions" | "readonly" {
|
||||
if (sourceKind === "step-review") return "verdicts";
|
||||
if (sourceKind && CONDITION_EDITABLE_KINDS.has(sourceKind)) return "conditions";
|
||||
return "readonly";
|
||||
}
|
||||
|
||||
/** True when adding an edge source→target would create a cycle, i.e. `target`
|
||||
* can already reach `source` by walking existing non-rework edges (rework edges
|
||||
* are the only legal cycles and are excluded from the reachability walk, per
|
||||
* KTD-9). Pure + exported so the connect-time guard is testable at the mapping
|
||||
* layer. */
|
||||
export function wouldCreateCycle(edges: FlowEdge[], source: string, target: string): boolean {
|
||||
if (source === target) return true;
|
||||
// Build adjacency over non-rework edges only.
|
||||
const adj = new Map<string, string[]>();
|
||||
for (const e of edges) {
|
||||
if ((e.data?.kind as string | undefined) === "rework") continue;
|
||||
const arr = adj.get(e.source) ?? [];
|
||||
arr.push(e.target);
|
||||
adj.set(e.source, arr);
|
||||
}
|
||||
// Can `target` reach `source`? If so, the new source→target edge closes a loop.
|
||||
const seen = new Set<string>();
|
||||
const stack = [target];
|
||||
while (stack.length) {
|
||||
const cur = stack.pop()!;
|
||||
if (cur === source) return true;
|
||||
if (seen.has(cur)) continue;
|
||||
seen.add(cur);
|
||||
for (const next of adj.get(cur) ?? []) stack.push(next);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let edgeSeq = 0;
|
||||
/** Allocate a globally-unique edge id (mirrors the editor's newNodeId pattern).
|
||||
* Used by buildConnectionEdge so parallel success+failure edges between the same
|
||||
* pair don't collide (KTD-3). */
|
||||
export function newEdgeId(): string {
|
||||
edgeSeq += 1;
|
||||
return `e-${Date.now().toString(36)}-${edgeSeq}`;
|
||||
}
|
||||
|
||||
/** Result of attempting to build an edge from a React Flow connection. */
|
||||
export type BuildConnectionResult =
|
||||
| { edge: FlowEdge }
|
||||
| { error: "missing-endpoint" | "duplicate" | "cycle" };
|
||||
|
||||
/** Construct a new success edge for a React Flow connection, reimplementing the
|
||||
* sanity guards React Flow's addEdge provided (KTD-3) plus the author-time cycle
|
||||
* guard (KTD-9). Returns an error tag instead of an edge when the connection is
|
||||
* rejected so the caller can surface a toast:
|
||||
* - missing-endpoint: source or target absent;
|
||||
* - duplicate: an edge with the same source+target+condition already exists
|
||||
* (parallel edges of a DIFFERENT condition between the same pair ARE allowed);
|
||||
* - cycle: the connection would close a non-rework loop, and the endpoints are
|
||||
* not both children of the same foreach template (intra-template rework
|
||||
* cycles are authored separately and exempt).
|
||||
*/
|
||||
export function buildConnectionEdge(
|
||||
connection: { source?: string | null; target?: string | null },
|
||||
edges: FlowEdge[],
|
||||
nodes: FlowNode<WorkflowFlowNodeData>[],
|
||||
): BuildConnectionResult {
|
||||
const source = connection.source ?? undefined;
|
||||
const target = connection.target ?? undefined;
|
||||
if (!source || !target) return { error: "missing-endpoint" };
|
||||
|
||||
const condition = "success";
|
||||
// Skip exact duplicates of the SAME condition (a second identical edge is
|
||||
// pointless); different conditions between the same pair are allowed.
|
||||
const isDuplicate = edges.some(
|
||||
(e) =>
|
||||
e.source === source &&
|
||||
e.target === target &&
|
||||
((e.data?.condition as string | undefined) ?? "success") === condition,
|
||||
);
|
||||
if (isDuplicate) return { error: "duplicate" };
|
||||
|
||||
// Cycle guard (KTD-9). Exempt connections where both endpoints are children of
|
||||
// the same foreach template — those may legitimately be rework cycles authored
|
||||
// separately; the simplest correct rule applies the guard only to non-template
|
||||
// connections.
|
||||
const srcNode = nodes.find((n) => n.id === source);
|
||||
const tgtNode = nodes.find((n) => n.id === target);
|
||||
const bothTemplateChildren =
|
||||
!!srcNode?.parentId && srcNode.parentId === tgtNode?.parentId;
|
||||
if (!bothTemplateChildren && wouldCreateCycle(edges, source, target)) {
|
||||
return { error: "cycle" };
|
||||
}
|
||||
|
||||
return {
|
||||
edge: {
|
||||
id: newEdgeId(),
|
||||
source,
|
||||
target,
|
||||
label: shortConditionLabel(condition),
|
||||
data: { condition, kind: undefined },
|
||||
className: edgeClassName(condition, false),
|
||||
interactionWidth: WF_EDGE_INTERACTION_WIDTH,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ── Client-side validation (U10) ─────────────────────────────────────────────
|
||||
//
|
||||
// The server's parseWorkflowIr (run on PATCH) is the authority for structural
|
||||
|
||||
@@ -6112,6 +6112,12 @@
|
||||
"switchToPlainText": "Switch to plain text",
|
||||
"yes": "Yes"
|
||||
},
|
||||
"taskFields": {
|
||||
"moreFields": "Additional fields",
|
||||
"orphaned": "Orphaned fields",
|
||||
"saveFailed": "Failed to save field",
|
||||
"unset": "—"
|
||||
},
|
||||
"taskForm": {
|
||||
"addDependencies": "Add dependencies",
|
||||
"attachHint": "You can also paste images or drag & drop",
|
||||
@@ -6734,8 +6740,8 @@
|
||||
"optionColor": "Option color",
|
||||
"optionLabel": "Option label",
|
||||
"optionN": "Option {{n}}",
|
||||
"optionValue": "Option value",
|
||||
"options": "Options",
|
||||
"optionValue": "Option value",
|
||||
"placement": "Placement",
|
||||
"placementCard": "Card badge",
|
||||
"placementDetail": "Detail (inline)",
|
||||
@@ -6754,6 +6760,8 @@
|
||||
"codeNote": "Runs sandboxed TypeScript. Syntax is validated at save.",
|
||||
"codeSource": "Source (TypeScript)",
|
||||
"codeTimeout": "Timeout (ms)",
|
||||
"cycleBlocked": "That connection would create a cycle — only rework edges inside a for-each template may loop back",
|
||||
"edgeCondition": "Condition",
|
||||
"edgeConditionLabel": "Condition: {{condition}}",
|
||||
"edgeInspector": "Edge",
|
||||
"edgeNoVerdict": "— success (no verdict) —",
|
||||
@@ -6775,6 +6783,7 @@
|
||||
"foreachWorktree": "Per-step worktree",
|
||||
"gateBlocks": "Gate (blocks)",
|
||||
"gateMode": "Gate mode",
|
||||
"interpreterOnly": "This workflow branches, so it runs on the graph interpreter — it can't compile to the linear step engine, but it will still run.",
|
||||
"joinAll": "All branches",
|
||||
"joinAny": "Any branch",
|
||||
"joinMode": "Join mode",
|
||||
@@ -6795,14 +6804,7 @@
|
||||
"reviewPlan": "Plan review",
|
||||
"reviewType": "Review type",
|
||||
"splitNote": "Branches run concurrently from this node. Execute and merge seams are not allowed inside a branch.",
|
||||
"stepExecuteLabel": "Step execute",
|
||||
"summaryAwaitInput": "Waits for user input",
|
||||
"summaryCodeDefault": "TypeScript",
|
||||
"summaryGateAdvisory": "Advisory",
|
||||
"summaryGateBlocks": "Gate (blocks)",
|
||||
"summaryHoldRelease": "Release: {{release}}",
|
||||
"summaryNotConfigured": "Not configured",
|
||||
"summaryReviewType": "{{type}} review"
|
||||
"stepExecuteLabel": "Step execute"
|
||||
},
|
||||
"workflows": {
|
||||
"duplicateToCustomize": "Duplicate to customize",
|
||||
@@ -6833,11 +6835,5 @@
|
||||
"installRequestTitle": "Worktrunk install request",
|
||||
"sha256": "SHA-256",
|
||||
"version": "Version"
|
||||
},
|
||||
"taskFields": {
|
||||
"unset": "—",
|
||||
"moreFields": "Additional fields",
|
||||
"orphaned": "Orphaned fields",
|
||||
"saveFailed": "Failed to save field"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
{
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"save": "Save"
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "↓ Declining",
|
||||
@@ -34,7 +29,6 @@
|
||||
"minutesAgo_other": "{{count}}m ago"
|
||||
}
|
||||
},
|
||||
"archive": "Archive",
|
||||
"board": {
|
||||
"rejection": {
|
||||
"capacityExhausted": "That column is at capacity. Try again when a slot frees up.",
|
||||
@@ -44,7 +38,6 @@
|
||||
"workflowMismatch": "Drag can't move a card between workflows. Use the workflow switcher instead."
|
||||
}
|
||||
},
|
||||
"cancel": "Cancel",
|
||||
"chat": {
|
||||
"failedToGetResponse": "Failed to get response",
|
||||
"failureReferenceId": "ID",
|
||||
@@ -54,25 +47,15 @@
|
||||
"openMailboxMessage": "Open mailbox message",
|
||||
"toolCallArgsPrefix": "args",
|
||||
"toolCallResultPrefix": "result",
|
||||
"toolCallsCount_one": "{{count}} tool calls",
|
||||
"toolCallsCount_other": "{{count}} tool calls",
|
||||
"toolCallsHeader": "Tool calls",
|
||||
"toolCallStatusCompleted": "completed",
|
||||
"toolCallStatusError": "error",
|
||||
"toolCallStatusErrors": "errors",
|
||||
"toolCallStatusRunning": "running",
|
||||
"toolCallsCount_one": "{{count}} tool calls",
|
||||
"toolCallsCount_other": "{{count}} tool calls",
|
||||
"toolCallsHeader": "Tool calls",
|
||||
"viewFailureDetails": "View failure details"
|
||||
},
|
||||
"close": "Close",
|
||||
"columns": {
|
||||
"archived": "Archived",
|
||||
"done": "Done",
|
||||
"in-progress": "In Progress",
|
||||
"in-review": "In Review",
|
||||
"todo": "Todo",
|
||||
"triage": "Planning"
|
||||
},
|
||||
"delete": "Delete",
|
||||
"health": {
|
||||
"anomaly": {
|
||||
"duplicateActiveId": "Duplicate active task ID",
|
||||
@@ -110,13 +93,6 @@
|
||||
"modelSetToDefault": "{{label}} model set to default"
|
||||
}
|
||||
},
|
||||
"nodeStatus": {
|
||||
"connecting": "Connecting",
|
||||
"error": "Error",
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"nodes": {
|
||||
"auth": {
|
||||
"differ": "Auth credentials differ",
|
||||
@@ -137,7 +113,13 @@
|
||||
"stopped": "Stopped"
|
||||
}
|
||||
},
|
||||
"refresh": "Refresh",
|
||||
"nodeStatus": {
|
||||
"connecting": "Connecting",
|
||||
"error": "Error",
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"research": {
|
||||
"providerGitHub": "GitHub",
|
||||
"providerLlmSynthesis": "LLM Synthesis",
|
||||
@@ -145,7 +127,6 @@
|
||||
"providerPageFetch": "Page Fetch",
|
||||
"providerWebSearch": "Web Search"
|
||||
},
|
||||
"retry": "Retry",
|
||||
"routing": {
|
||||
"policyLabel": {
|
||||
"block": "Block execution",
|
||||
@@ -205,7 +186,6 @@
|
||||
"zai": "GLM models by Zhipu AI — strong multilingual support"
|
||||
}
|
||||
},
|
||||
"skip": "Skip",
|
||||
"taskForm": {
|
||||
"nodeStatusConnecting": "Connecting",
|
||||
"nodeStatusError": "Error",
|
||||
@@ -220,7 +200,6 @@
|
||||
"refreshSourceInitialLoad": "Initial load",
|
||||
"refreshSourceManual": "Manual"
|
||||
},
|
||||
"tryAgain": "Try Again",
|
||||
"workflow": {
|
||||
"postMerge": "Post-merge",
|
||||
"preMerge": "Pre-merge",
|
||||
@@ -230,5 +209,14 @@
|
||||
"statusRunning": "Running…",
|
||||
"statusSkipped": "Skipped",
|
||||
"waitingForOutput": "Waiting for agent output…"
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "Waits for user input",
|
||||
"summaryCodeDefault": "TypeScript",
|
||||
"summaryGateAdvisory": "Advisory",
|
||||
"summaryGateBlocks": "Gate (blocks)",
|
||||
"summaryHoldRelease": "Release: {{release}}",
|
||||
"summaryNotConfigured": "Not configured",
|
||||
"summaryReviewType": "{{type}} review"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6112,6 +6112,12 @@
|
||||
"switchToPlainText": "Cambiar a texto sin formato",
|
||||
"yes": "Sí"
|
||||
},
|
||||
"taskFields": {
|
||||
"moreFields": "Campos adicionales",
|
||||
"orphaned": "Campos huérfanos",
|
||||
"saveFailed": "No se pudo guardar el campo",
|
||||
"unset": "—"
|
||||
},
|
||||
"taskForm": {
|
||||
"addDependencies": "Agregar dependencias",
|
||||
"attachHint": "También puedes pegar imágenes o arrastrar y soltar",
|
||||
@@ -6715,11 +6721,47 @@
|
||||
"unplacedCount_one": "",
|
||||
"unplacedCount_other": ""
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "Agregar campo",
|
||||
"addOption": "Agregar opción",
|
||||
"badge": "Mostrar como insignia",
|
||||
"default": "Predeterminado",
|
||||
"defaultLabel": "Valor predeterminado",
|
||||
"defaultTrue": "Activado por defecto",
|
||||
"duplicateId": "Ya existe un campo con ese id",
|
||||
"editId": "Editar id",
|
||||
"empty": "Aún no hay campos personalizados. Agrega un campo para ampliar el formulario y las tarjetas de la tarea.",
|
||||
"idLabel": "Id del campo",
|
||||
"idWarn": "Cambiar el id descarta los valores almacenados con el id anterior (quitar + agregar).",
|
||||
"nameLabel": "Nombre del campo",
|
||||
"newFieldName": "Nuevo campo",
|
||||
"newOptionLabel": "Opción 1",
|
||||
"noDefault": "— ninguno —",
|
||||
"optionColor": "Color de la opción",
|
||||
"optionLabel": "Etiqueta de la opción",
|
||||
"optionN": "Opción {{n}}",
|
||||
"options": "Opciones",
|
||||
"optionValue": "Valor de la opción",
|
||||
"placement": "Ubicación",
|
||||
"placementCard": "Insignia de tarjeta",
|
||||
"placementDetail": "Detalle (en línea)",
|
||||
"placementSection": "Sección de detalle",
|
||||
"readOnlyHint": "Los flujos de trabajo integrados son de solo lectura: duplica para editar",
|
||||
"remove": "Quitar campo",
|
||||
"removeOption": "Quitar opción",
|
||||
"required": "Obligatorio",
|
||||
"title": "Campos",
|
||||
"typeLabel": "Tipo",
|
||||
"widget": "Control",
|
||||
"widgetDefault": "Predeterminado"
|
||||
},
|
||||
"workflowNodes": {
|
||||
"advisory": "",
|
||||
"codeNote": "Ejecuta TypeScript en un entorno aislado. La sintaxis se valida al guardar.",
|
||||
"codeSource": "Origen (TypeScript)",
|
||||
"codeTimeout": "Tiempo de espera (ms)",
|
||||
"cycleBlocked": "",
|
||||
"edgeCondition": "",
|
||||
"edgeConditionLabel": "Condición: {{condition}}",
|
||||
"edgeInspector": "Conexión",
|
||||
"edgeNoVerdict": "— éxito (sin veredicto) —",
|
||||
@@ -6741,6 +6783,7 @@
|
||||
"foreachWorktree": "Árbol de trabajo por paso",
|
||||
"gateBlocks": "Compuerta (bloquea)",
|
||||
"gateMode": "Modo de compuerta",
|
||||
"interpreterOnly": "",
|
||||
"joinAll": "Todas las ramas",
|
||||
"joinAny": "Cualquier rama",
|
||||
"joinMode": "Modo de unión",
|
||||
@@ -6761,14 +6804,7 @@
|
||||
"reviewPlan": "Plan review",
|
||||
"reviewType": "Review type",
|
||||
"splitNote": "Las ramas se ejecutan de forma concurrente desde este nodo. No se permiten uniones de ejecución y fusión dentro de una rama.",
|
||||
"stepExecuteLabel": "Step execute",
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
"summaryGateAdvisory": "",
|
||||
"summaryGateBlocks": "",
|
||||
"summaryHoldRelease": "",
|
||||
"summaryNotConfigured": "",
|
||||
"summaryReviewType": ""
|
||||
"stepExecuteLabel": "Step execute"
|
||||
},
|
||||
"workflows": {
|
||||
"duplicateToCustomize": "",
|
||||
@@ -6799,45 +6835,5 @@
|
||||
"installRequestTitle": "Solicitud de instalación de Worktrunk",
|
||||
"sha256": "SHA-256",
|
||||
"version": "Versión"
|
||||
},
|
||||
"taskFields": {
|
||||
"unset": "—",
|
||||
"moreFields": "Campos adicionales",
|
||||
"orphaned": "Campos huérfanos",
|
||||
"saveFailed": "No se pudo guardar el campo"
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "Agregar campo",
|
||||
"addOption": "Agregar opción",
|
||||
"badge": "Mostrar como insignia",
|
||||
"default": "Predeterminado",
|
||||
"defaultLabel": "Valor predeterminado",
|
||||
"defaultTrue": "Activado por defecto",
|
||||
"duplicateId": "Ya existe un campo con ese id",
|
||||
"editId": "Editar id",
|
||||
"empty": "Aún no hay campos personalizados. Agrega un campo para ampliar el formulario y las tarjetas de la tarea.",
|
||||
"idLabel": "Id del campo",
|
||||
"idWarn": "Cambiar el id descarta los valores almacenados con el id anterior (quitar + agregar).",
|
||||
"nameLabel": "Nombre del campo",
|
||||
"newFieldName": "Nuevo campo",
|
||||
"newOptionLabel": "Opción 1",
|
||||
"noDefault": "— ninguno —",
|
||||
"optionColor": "Color de la opción",
|
||||
"optionLabel": "Etiqueta de la opción",
|
||||
"optionN": "Opción {{n}}",
|
||||
"optionValue": "Valor de la opción",
|
||||
"options": "Opciones",
|
||||
"placement": "Ubicación",
|
||||
"placementCard": "Insignia de tarjeta",
|
||||
"placementDetail": "Detalle (en línea)",
|
||||
"placementSection": "Sección de detalle",
|
||||
"readOnlyHint": "Los flujos de trabajo integrados son de solo lectura: duplica para editar",
|
||||
"remove": "Quitar campo",
|
||||
"removeOption": "Quitar opción",
|
||||
"required": "Obligatorio",
|
||||
"title": "Campos",
|
||||
"typeLabel": "Tipo",
|
||||
"widget": "Control",
|
||||
"widgetDefault": "Predeterminado"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
{
|
||||
"actions": {
|
||||
"cancel": "Cancelar",
|
||||
"close": "Cerrar",
|
||||
"save": "Guardar"
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "",
|
||||
@@ -34,7 +29,6 @@
|
||||
"minutesAgo_other": ""
|
||||
}
|
||||
},
|
||||
"archive": "Archivar",
|
||||
"board": {
|
||||
"rejection": {
|
||||
"capacityExhausted": "",
|
||||
@@ -44,7 +38,6 @@
|
||||
"workflowMismatch": ""
|
||||
}
|
||||
},
|
||||
"cancel": "Cancelar",
|
||||
"chat": {
|
||||
"failedToGetResponse": "",
|
||||
"failureReferenceId": "",
|
||||
@@ -54,25 +47,15 @@
|
||||
"openMailboxMessage": "",
|
||||
"toolCallArgsPrefix": "",
|
||||
"toolCallResultPrefix": "",
|
||||
"toolCallsCount_one": "",
|
||||
"toolCallsCount_other": "",
|
||||
"toolCallsHeader": "",
|
||||
"toolCallStatusCompleted": "",
|
||||
"toolCallStatusError": "",
|
||||
"toolCallStatusErrors": "",
|
||||
"toolCallStatusRunning": "",
|
||||
"toolCallsCount_one": "",
|
||||
"toolCallsCount_other": "",
|
||||
"toolCallsHeader": "",
|
||||
"viewFailureDetails": ""
|
||||
},
|
||||
"close": "Cerrar",
|
||||
"columns": {
|
||||
"archived": "Archivado",
|
||||
"done": "Hecho",
|
||||
"in-progress": "En progreso",
|
||||
"in-review": "En revisión",
|
||||
"todo": "Por hacer",
|
||||
"triage": "Planificación"
|
||||
},
|
||||
"delete": "Eliminar",
|
||||
"health": {
|
||||
"anomaly": {
|
||||
"duplicateActiveId": "",
|
||||
@@ -110,13 +93,6 @@
|
||||
"modelSetToDefault": ""
|
||||
}
|
||||
},
|
||||
"nodeStatus": {
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"offline": "",
|
||||
"online": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"nodes": {
|
||||
"auth": {
|
||||
"differ": "",
|
||||
@@ -137,7 +113,13 @@
|
||||
"stopped": ""
|
||||
}
|
||||
},
|
||||
"refresh": "Actualizar",
|
||||
"nodeStatus": {
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"offline": "",
|
||||
"online": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"research": {
|
||||
"providerGitHub": "",
|
||||
"providerLlmSynthesis": "",
|
||||
@@ -145,7 +127,6 @@
|
||||
"providerPageFetch": "",
|
||||
"providerWebSearch": ""
|
||||
},
|
||||
"retry": "Reintentar",
|
||||
"routing": {
|
||||
"policyLabel": {
|
||||
"block": "",
|
||||
@@ -205,7 +186,6 @@
|
||||
"zai": ""
|
||||
}
|
||||
},
|
||||
"skip": "Omitir",
|
||||
"taskForm": {
|
||||
"nodeStatusConnecting": "",
|
||||
"nodeStatusError": "",
|
||||
@@ -220,7 +200,6 @@
|
||||
"refreshSourceInitialLoad": "",
|
||||
"refreshSourceManual": ""
|
||||
},
|
||||
"tryAgain": "Reintentar",
|
||||
"workflow": {
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
@@ -230,5 +209,14 @@
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
"summaryGateAdvisory": "",
|
||||
"summaryGateBlocks": "",
|
||||
"summaryHoldRelease": "",
|
||||
"summaryNotConfigured": "",
|
||||
"summaryReviewType": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6112,6 +6112,12 @@
|
||||
"switchToPlainText": "Basculer vers du texte brut",
|
||||
"yes": "Oui"
|
||||
},
|
||||
"taskFields": {
|
||||
"moreFields": "Champs supplémentaires",
|
||||
"orphaned": "Champs orphelins",
|
||||
"saveFailed": "Échec de l'enregistrement du champ",
|
||||
"unset": "—"
|
||||
},
|
||||
"taskForm": {
|
||||
"addDependencies": "Ajouter des dépendances",
|
||||
"attachHint": "Vous pouvez aussi coller des images ou les glisser-déposer",
|
||||
@@ -6715,11 +6721,47 @@
|
||||
"unplacedCount_one": "",
|
||||
"unplacedCount_other": ""
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "Ajouter un champ",
|
||||
"addOption": "Ajouter une option",
|
||||
"badge": "Afficher comme badge",
|
||||
"default": "Par défaut",
|
||||
"defaultLabel": "Valeur par défaut",
|
||||
"defaultTrue": "Activé par défaut",
|
||||
"duplicateId": "Un champ avec cet id existe déjà",
|
||||
"editId": "Modifier l'id",
|
||||
"empty": "Aucun champ personnalisé pour l'instant. Ajoutez un champ pour étendre le formulaire et les cartes de la tâche.",
|
||||
"idLabel": "Id du champ",
|
||||
"idWarn": "Modifier l'id supprime les valeurs stockées sous l'ancien id (retirer + ajouter).",
|
||||
"nameLabel": "Nom du champ",
|
||||
"newFieldName": "Nouveau champ",
|
||||
"newOptionLabel": "Option 1",
|
||||
"noDefault": "— aucun —",
|
||||
"optionColor": "Couleur de l'option",
|
||||
"optionLabel": "Libellé de l'option",
|
||||
"optionN": "Option {{n}}",
|
||||
"options": "Options",
|
||||
"optionValue": "Valeur de l'option",
|
||||
"placement": "Emplacement",
|
||||
"placementCard": "Badge de carte",
|
||||
"placementDetail": "Détail (en ligne)",
|
||||
"placementSection": "Section de détail",
|
||||
"readOnlyHint": "Les flux de travail intégrés sont en lecture seule — dupliquez pour modifier",
|
||||
"remove": "Retirer le champ",
|
||||
"removeOption": "Retirer l'option",
|
||||
"required": "Obligatoire",
|
||||
"title": "Champs",
|
||||
"typeLabel": "Type",
|
||||
"widget": "Composant",
|
||||
"widgetDefault": "Par défaut"
|
||||
},
|
||||
"workflowNodes": {
|
||||
"advisory": "",
|
||||
"codeNote": "Exécute du TypeScript en bac à sable. La syntaxe est validée à l’enregistrement.",
|
||||
"codeSource": "Source (TypeScript)",
|
||||
"codeTimeout": "Délai d’expiration (ms)",
|
||||
"cycleBlocked": "",
|
||||
"edgeCondition": "",
|
||||
"edgeConditionLabel": "Condition : {{condition}}",
|
||||
"edgeInspector": "Lien",
|
||||
"edgeNoVerdict": "— succès (aucun verdict) —",
|
||||
@@ -6741,6 +6783,7 @@
|
||||
"foreachWorktree": "Arbre de travail par étape",
|
||||
"gateBlocks": "Barrière (bloque)",
|
||||
"gateMode": "Mode de barrière",
|
||||
"interpreterOnly": "",
|
||||
"joinAll": "Toutes les branches",
|
||||
"joinAny": "N’importe quelle branche",
|
||||
"joinMode": "Mode de jointure",
|
||||
@@ -6761,14 +6804,7 @@
|
||||
"reviewPlan": "Plan review",
|
||||
"reviewType": "Review type",
|
||||
"splitNote": "Les branches s’exécutent simultanément depuis ce nœud. Les jointures d’exécution et de fusion ne sont pas autorisées dans une branche.",
|
||||
"stepExecuteLabel": "Step execute",
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
"summaryGateAdvisory": "",
|
||||
"summaryGateBlocks": "",
|
||||
"summaryHoldRelease": "",
|
||||
"summaryNotConfigured": "",
|
||||
"summaryReviewType": ""
|
||||
"stepExecuteLabel": "Step execute"
|
||||
},
|
||||
"workflows": {
|
||||
"duplicateToCustomize": "",
|
||||
@@ -6799,45 +6835,5 @@
|
||||
"installRequestTitle": "Demande d'installation de Worktrunk",
|
||||
"sha256": "SHA-256",
|
||||
"version": "Version"
|
||||
},
|
||||
"taskFields": {
|
||||
"unset": "—",
|
||||
"moreFields": "Champs supplémentaires",
|
||||
"orphaned": "Champs orphelins",
|
||||
"saveFailed": "Échec de l'enregistrement du champ"
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "Ajouter un champ",
|
||||
"addOption": "Ajouter une option",
|
||||
"badge": "Afficher comme badge",
|
||||
"default": "Par défaut",
|
||||
"defaultLabel": "Valeur par défaut",
|
||||
"defaultTrue": "Activé par défaut",
|
||||
"duplicateId": "Un champ avec cet id existe déjà",
|
||||
"editId": "Modifier l'id",
|
||||
"empty": "Aucun champ personnalisé pour l'instant. Ajoutez un champ pour étendre le formulaire et les cartes de la tâche.",
|
||||
"idLabel": "Id du champ",
|
||||
"idWarn": "Modifier l'id supprime les valeurs stockées sous l'ancien id (retirer + ajouter).",
|
||||
"nameLabel": "Nom du champ",
|
||||
"newFieldName": "Nouveau champ",
|
||||
"newOptionLabel": "Option 1",
|
||||
"noDefault": "— aucun —",
|
||||
"optionColor": "Couleur de l'option",
|
||||
"optionLabel": "Libellé de l'option",
|
||||
"optionN": "Option {{n}}",
|
||||
"optionValue": "Valeur de l'option",
|
||||
"options": "Options",
|
||||
"placement": "Emplacement",
|
||||
"placementCard": "Badge de carte",
|
||||
"placementDetail": "Détail (en ligne)",
|
||||
"placementSection": "Section de détail",
|
||||
"readOnlyHint": "Les flux de travail intégrés sont en lecture seule — dupliquez pour modifier",
|
||||
"remove": "Retirer le champ",
|
||||
"removeOption": "Retirer l'option",
|
||||
"required": "Obligatoire",
|
||||
"title": "Champs",
|
||||
"typeLabel": "Type",
|
||||
"widget": "Composant",
|
||||
"widgetDefault": "Par défaut"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
{
|
||||
"actions": {
|
||||
"cancel": "Annuler",
|
||||
"close": "Fermer",
|
||||
"save": "Enregistrer"
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "",
|
||||
@@ -34,7 +29,6 @@
|
||||
"minutesAgo_other": ""
|
||||
}
|
||||
},
|
||||
"archive": "Archiver",
|
||||
"board": {
|
||||
"rejection": {
|
||||
"capacityExhausted": "",
|
||||
@@ -44,7 +38,6 @@
|
||||
"workflowMismatch": ""
|
||||
}
|
||||
},
|
||||
"cancel": "Annuler",
|
||||
"chat": {
|
||||
"failedToGetResponse": "",
|
||||
"failureReferenceId": "",
|
||||
@@ -54,25 +47,15 @@
|
||||
"openMailboxMessage": "",
|
||||
"toolCallArgsPrefix": "",
|
||||
"toolCallResultPrefix": "",
|
||||
"toolCallsCount_one": "",
|
||||
"toolCallsCount_other": "",
|
||||
"toolCallsHeader": "",
|
||||
"toolCallStatusCompleted": "",
|
||||
"toolCallStatusError": "",
|
||||
"toolCallStatusErrors": "",
|
||||
"toolCallStatusRunning": "",
|
||||
"toolCallsCount_one": "",
|
||||
"toolCallsCount_other": "",
|
||||
"toolCallsHeader": "",
|
||||
"viewFailureDetails": ""
|
||||
},
|
||||
"close": "Fermer",
|
||||
"columns": {
|
||||
"archived": "Archivé",
|
||||
"done": "Terminé",
|
||||
"in-progress": "En cours",
|
||||
"in-review": "En revue",
|
||||
"todo": "À faire",
|
||||
"triage": "Planification"
|
||||
},
|
||||
"delete": "Supprimer",
|
||||
"health": {
|
||||
"anomaly": {
|
||||
"duplicateActiveId": "",
|
||||
@@ -110,13 +93,6 @@
|
||||
"modelSetToDefault": ""
|
||||
}
|
||||
},
|
||||
"nodeStatus": {
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"offline": "",
|
||||
"online": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"nodes": {
|
||||
"auth": {
|
||||
"differ": "",
|
||||
@@ -137,7 +113,13 @@
|
||||
"stopped": ""
|
||||
}
|
||||
},
|
||||
"refresh": "Actualiser",
|
||||
"nodeStatus": {
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"offline": "",
|
||||
"online": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"research": {
|
||||
"providerGitHub": "",
|
||||
"providerLlmSynthesis": "",
|
||||
@@ -145,7 +127,6 @@
|
||||
"providerPageFetch": "",
|
||||
"providerWebSearch": ""
|
||||
},
|
||||
"retry": "Réessayer",
|
||||
"routing": {
|
||||
"policyLabel": {
|
||||
"block": "",
|
||||
@@ -205,7 +186,6 @@
|
||||
"zai": ""
|
||||
}
|
||||
},
|
||||
"skip": "Ignorer",
|
||||
"taskForm": {
|
||||
"nodeStatusConnecting": "",
|
||||
"nodeStatusError": "",
|
||||
@@ -220,7 +200,6 @@
|
||||
"refreshSourceInitialLoad": "",
|
||||
"refreshSourceManual": ""
|
||||
},
|
||||
"tryAgain": "Réessayer",
|
||||
"workflow": {
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
@@ -230,5 +209,14 @@
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
"summaryGateAdvisory": "",
|
||||
"summaryGateBlocks": "",
|
||||
"summaryHoldRelease": "",
|
||||
"summaryNotConfigured": "",
|
||||
"summaryReviewType": ""
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
"agentRunLogsBackHint": "[Esc/q] 실행 목록으로 돌아가기",
|
||||
"agentRunLogsTitle": "실행 로그 ({{index}})",
|
||||
"agentsFooterHints": "[s] 시작 [x] 중지 [D] 삭제 [r] 새로고침 [Tab] 포커스 ↑↓ 선택",
|
||||
"agentsListTitle_one": "",
|
||||
"agentsListTitle_other": "",
|
||||
"agentsNoAgents": "에이전트가 없습니다.",
|
||||
"agentStarted": "에이전트 시작됨",
|
||||
@@ -44,6 +45,7 @@
|
||||
"filesEmpty": "(비어 있음)",
|
||||
"filesEmptyFile": "(빈 파일)",
|
||||
"filesFooterHints": "[Tab] 창 전환 [↑↓/jk] 이동 [Enter] 열기 [←/→] 접기/펼치기 [.] 숨김 [w] 줄 바꿈 [p] 프로젝트 [r] 새로고침",
|
||||
"filesMoreLines_one": "",
|
||||
"filesMoreLines_other": "",
|
||||
"filesSelectProject": "프로젝트 선택",
|
||||
"filesSelectToPreview": "미리볼 파일을 선택하세요",
|
||||
@@ -151,6 +153,7 @@
|
||||
"settingsFooterHints": "[Tab] 패널 전환 ↑↓ 설정 선택 [Space] 불리언 토글 [+/-] 숫자 조정 [←/→] 열거형 순환 [C/V/X/P/L/U/K/R] 원격 작업",
|
||||
"settingsInteractivePanelTitle": "설정",
|
||||
"settingsLoadingSettings": "설정 불러오는 중…",
|
||||
"settingsMoreModels_one": "",
|
||||
"settingsMoreModels_other": "",
|
||||
"settingsPanelTitle": "설정",
|
||||
"settingsPersistentTokenRegenerated": "영구 토큰이 재생성됨",
|
||||
@@ -218,9 +221,6 @@
|
||||
"utilitiesKillVitest": "Vitest 프로세스 종료",
|
||||
"utilitiesPanelTitle": "유틸리티",
|
||||
"utilitiesRefreshStats": "통계 새로고침",
|
||||
"utilitiesToggleEnginePause": "엔진 일시 정지 전환",
|
||||
"agentsListTitle_one": "",
|
||||
"filesMoreLines_one": "",
|
||||
"settingsMoreModels_one": ""
|
||||
"utilitiesToggleEnginePause": "엔진 일시 정지 전환"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
{
|
||||
"actions": {
|
||||
"cancel": "취소",
|
||||
"close": "닫기",
|
||||
"save": "저장"
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "",
|
||||
@@ -34,7 +29,6 @@
|
||||
"minutesAgo_other": ""
|
||||
}
|
||||
},
|
||||
"archive": "보관",
|
||||
"board": {
|
||||
"rejection": {
|
||||
"capacityExhausted": "",
|
||||
@@ -44,7 +38,6 @@
|
||||
"workflowMismatch": ""
|
||||
}
|
||||
},
|
||||
"cancel": "취소",
|
||||
"chat": {
|
||||
"failedToGetResponse": "",
|
||||
"failureReferenceId": "",
|
||||
@@ -54,25 +47,15 @@
|
||||
"openMailboxMessage": "",
|
||||
"toolCallArgsPrefix": "",
|
||||
"toolCallResultPrefix": "",
|
||||
"toolCallsCount_one": "",
|
||||
"toolCallsCount_other": "",
|
||||
"toolCallsHeader": "",
|
||||
"toolCallStatusCompleted": "",
|
||||
"toolCallStatusError": "",
|
||||
"toolCallStatusErrors": "",
|
||||
"toolCallStatusRunning": "",
|
||||
"toolCallsCount_one": "",
|
||||
"toolCallsCount_other": "",
|
||||
"toolCallsHeader": "",
|
||||
"viewFailureDetails": ""
|
||||
},
|
||||
"close": "닫기",
|
||||
"columns": {
|
||||
"archived": "보관됨",
|
||||
"done": "완료",
|
||||
"in-progress": "진행 중",
|
||||
"in-review": "검토 중",
|
||||
"todo": "할 일",
|
||||
"triage": "계획"
|
||||
},
|
||||
"delete": "삭제",
|
||||
"health": {
|
||||
"anomaly": {
|
||||
"duplicateActiveId": "",
|
||||
@@ -110,13 +93,6 @@
|
||||
"modelSetToDefault": ""
|
||||
}
|
||||
},
|
||||
"nodeStatus": {
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"offline": "",
|
||||
"online": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"nodes": {
|
||||
"auth": {
|
||||
"differ": "",
|
||||
@@ -137,7 +113,13 @@
|
||||
"stopped": ""
|
||||
}
|
||||
},
|
||||
"refresh": "새로고침",
|
||||
"nodeStatus": {
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"offline": "",
|
||||
"online": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"research": {
|
||||
"providerGitHub": "",
|
||||
"providerLlmSynthesis": "",
|
||||
@@ -145,7 +127,6 @@
|
||||
"providerPageFetch": "",
|
||||
"providerWebSearch": ""
|
||||
},
|
||||
"retry": "재시도",
|
||||
"routing": {
|
||||
"policyLabel": {
|
||||
"block": "",
|
||||
@@ -205,7 +186,6 @@
|
||||
"zai": ""
|
||||
}
|
||||
},
|
||||
"skip": "건너뛰기",
|
||||
"taskForm": {
|
||||
"nodeStatusConnecting": "",
|
||||
"nodeStatusError": "",
|
||||
@@ -220,7 +200,6 @@
|
||||
"refreshSourceInitialLoad": "",
|
||||
"refreshSourceManual": ""
|
||||
},
|
||||
"tryAgain": "다시 시도",
|
||||
"workflow": {
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
@@ -230,5 +209,14 @@
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
"summaryGateAdvisory": "",
|
||||
"summaryGateBlocks": "",
|
||||
"summaryHoldRelease": "",
|
||||
"summaryNotConfigured": "",
|
||||
"summaryReviewType": ""
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
"agentRunLogsBackHint": "[Esc/q] 返回运行列表",
|
||||
"agentRunLogsTitle": "运行日志({{index}})",
|
||||
"agentsFooterHints": "[s] 启动 [x] 停止 [D] 删除 [r] 刷新 [Tab] 焦点 ↑↓ 选择",
|
||||
"agentsListTitle_one": "",
|
||||
"agentsListTitle_other": "",
|
||||
"agentsNoAgents": "未找到代理。",
|
||||
"agentStarted": "代理已启动",
|
||||
@@ -44,6 +45,7 @@
|
||||
"filesEmpty": "(空)",
|
||||
"filesEmptyFile": "(空文件)",
|
||||
"filesFooterHints": "[Tab] 切换面板 [↑↓/jk] 移动 [Enter] 打开 [←/→] 折叠/展开 [.] 隐藏文件 [w] 换行 [p] 项目 [r] 重载",
|
||||
"filesMoreLines_one": "",
|
||||
"filesMoreLines_other": "",
|
||||
"filesSelectProject": "选择项目",
|
||||
"filesSelectToPreview": "选择文件以预览",
|
||||
@@ -151,6 +153,7 @@
|
||||
"settingsFooterHints": "[Tab] 切换面板 ↑↓ 选择设置 [Space] 切换布尔 [+/-] 调整数值 [←/→] 循环枚举 [C/V/X/P/L/U/K/R] 远程操作",
|
||||
"settingsInteractivePanelTitle": "设置",
|
||||
"settingsLoadingSettings": "正在加载设置…",
|
||||
"settingsMoreModels_one": "",
|
||||
"settingsMoreModels_other": "",
|
||||
"settingsPanelTitle": "设置",
|
||||
"settingsPersistentTokenRegenerated": "持久令牌已重新生成",
|
||||
@@ -218,9 +221,6 @@
|
||||
"utilitiesKillVitest": "终止 Vitest 进程",
|
||||
"utilitiesPanelTitle": "工具",
|
||||
"utilitiesRefreshStats": "刷新统计",
|
||||
"utilitiesToggleEnginePause": "切换引擎暂停",
|
||||
"agentsListTitle_one": "",
|
||||
"filesMoreLines_one": "",
|
||||
"settingsMoreModels_one": ""
|
||||
"utilitiesToggleEnginePause": "切换引擎暂停"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
{
|
||||
"actions": {
|
||||
"cancel": "取消",
|
||||
"close": "关闭",
|
||||
"save": "保存"
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "",
|
||||
@@ -34,7 +29,6 @@
|
||||
"minutesAgo_other": ""
|
||||
}
|
||||
},
|
||||
"archive": "归档",
|
||||
"board": {
|
||||
"rejection": {
|
||||
"capacityExhausted": "",
|
||||
@@ -44,7 +38,6 @@
|
||||
"workflowMismatch": ""
|
||||
}
|
||||
},
|
||||
"cancel": "取消",
|
||||
"chat": {
|
||||
"failedToGetResponse": "",
|
||||
"failureReferenceId": "",
|
||||
@@ -54,25 +47,15 @@
|
||||
"openMailboxMessage": "",
|
||||
"toolCallArgsPrefix": "",
|
||||
"toolCallResultPrefix": "",
|
||||
"toolCallsCount_one": "",
|
||||
"toolCallsCount_other": "",
|
||||
"toolCallsHeader": "",
|
||||
"toolCallStatusCompleted": "",
|
||||
"toolCallStatusError": "",
|
||||
"toolCallStatusErrors": "",
|
||||
"toolCallStatusRunning": "",
|
||||
"toolCallsCount_one": "",
|
||||
"toolCallsCount_other": "",
|
||||
"toolCallsHeader": "",
|
||||
"viewFailureDetails": ""
|
||||
},
|
||||
"close": "关闭",
|
||||
"columns": {
|
||||
"archived": "已归档",
|
||||
"done": "已完成",
|
||||
"in-progress": "进行中",
|
||||
"in-review": "审核中",
|
||||
"todo": "待办",
|
||||
"triage": "规划"
|
||||
},
|
||||
"delete": "删除",
|
||||
"health": {
|
||||
"anomaly": {
|
||||
"duplicateActiveId": "",
|
||||
@@ -110,13 +93,6 @@
|
||||
"modelSetToDefault": ""
|
||||
}
|
||||
},
|
||||
"nodeStatus": {
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"offline": "",
|
||||
"online": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"nodes": {
|
||||
"auth": {
|
||||
"differ": "",
|
||||
@@ -137,7 +113,13 @@
|
||||
"stopped": ""
|
||||
}
|
||||
},
|
||||
"refresh": "刷新",
|
||||
"nodeStatus": {
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"offline": "",
|
||||
"online": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"research": {
|
||||
"providerGitHub": "",
|
||||
"providerLlmSynthesis": "",
|
||||
@@ -145,7 +127,6 @@
|
||||
"providerPageFetch": "",
|
||||
"providerWebSearch": ""
|
||||
},
|
||||
"retry": "重试",
|
||||
"routing": {
|
||||
"policyLabel": {
|
||||
"block": "",
|
||||
@@ -205,7 +186,6 @@
|
||||
"zai": ""
|
||||
}
|
||||
},
|
||||
"skip": "跳过",
|
||||
"taskForm": {
|
||||
"nodeStatusConnecting": "",
|
||||
"nodeStatusError": "",
|
||||
@@ -220,7 +200,6 @@
|
||||
"refreshSourceInitialLoad": "",
|
||||
"refreshSourceManual": ""
|
||||
},
|
||||
"tryAgain": "重试",
|
||||
"workflow": {
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
@@ -230,5 +209,14 @@
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
"summaryGateAdvisory": "",
|
||||
"summaryGateBlocks": "",
|
||||
"summaryHoldRelease": "",
|
||||
"summaryNotConfigured": "",
|
||||
"summaryReviewType": ""
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
"agentRunLogsBackHint": "[Esc/q] 返回執行列表",
|
||||
"agentRunLogsTitle": "執行日誌({{index}})",
|
||||
"agentsFooterHints": "[s] 啟動 [x] 停止 [D] 刪除 [r] 重新整理 [Tab] 焦點 ↑↓ 選擇",
|
||||
"agentsListTitle_one": "",
|
||||
"agentsListTitle_other": "",
|
||||
"agentsNoAgents": "找不到代理。",
|
||||
"agentStarted": "代理已啟動",
|
||||
@@ -44,6 +45,7 @@
|
||||
"filesEmpty": "(空)",
|
||||
"filesEmptyFile": "(空檔案)",
|
||||
"filesFooterHints": "[Tab] 切換面板 [↑↓/jk] 移動 [Enter] 開啟 [←/→] 折疊/展開 [.] 隱藏檔案 [w] 換行 [p] 專案 [r] 重新載入",
|
||||
"filesMoreLines_one": "",
|
||||
"filesMoreLines_other": "",
|
||||
"filesSelectProject": "選擇專案",
|
||||
"filesSelectToPreview": "選擇檔案以預覽",
|
||||
@@ -151,6 +153,7 @@
|
||||
"settingsFooterHints": "[Tab] 切換面板 ↑↓ 選擇設定 [Space] 切換布林 [+/-] 調整數值 [←/→] 循環枚舉 [C/V/X/P/L/U/K/R] 遠端操作",
|
||||
"settingsInteractivePanelTitle": "設定",
|
||||
"settingsLoadingSettings": "正在載入設定…",
|
||||
"settingsMoreModels_one": "",
|
||||
"settingsMoreModels_other": "",
|
||||
"settingsPanelTitle": "設定",
|
||||
"settingsPersistentTokenRegenerated": "持久金鑰已重新產生",
|
||||
@@ -218,9 +221,6 @@
|
||||
"utilitiesKillVitest": "終止 Vitest 行程",
|
||||
"utilitiesPanelTitle": "工具",
|
||||
"utilitiesRefreshStats": "重新整理統計",
|
||||
"utilitiesToggleEnginePause": "切換引擎暫停",
|
||||
"agentsListTitle_one": "",
|
||||
"filesMoreLines_one": "",
|
||||
"settingsMoreModels_one": ""
|
||||
"utilitiesToggleEnginePause": "切換引擎暫停"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
{
|
||||
"actions": {
|
||||
"cancel": "取消",
|
||||
"close": "關閉",
|
||||
"save": "儲存"
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "",
|
||||
@@ -34,7 +29,6 @@
|
||||
"minutesAgo_other": ""
|
||||
}
|
||||
},
|
||||
"archive": "封存",
|
||||
"board": {
|
||||
"rejection": {
|
||||
"capacityExhausted": "",
|
||||
@@ -44,7 +38,6 @@
|
||||
"workflowMismatch": ""
|
||||
}
|
||||
},
|
||||
"cancel": "取消",
|
||||
"chat": {
|
||||
"failedToGetResponse": "",
|
||||
"failureReferenceId": "",
|
||||
@@ -54,25 +47,15 @@
|
||||
"openMailboxMessage": "",
|
||||
"toolCallArgsPrefix": "",
|
||||
"toolCallResultPrefix": "",
|
||||
"toolCallsCount_one": "",
|
||||
"toolCallsCount_other": "",
|
||||
"toolCallsHeader": "",
|
||||
"toolCallStatusCompleted": "",
|
||||
"toolCallStatusError": "",
|
||||
"toolCallStatusErrors": "",
|
||||
"toolCallStatusRunning": "",
|
||||
"toolCallsCount_one": "",
|
||||
"toolCallsCount_other": "",
|
||||
"toolCallsHeader": "",
|
||||
"viewFailureDetails": ""
|
||||
},
|
||||
"close": "關閉",
|
||||
"columns": {
|
||||
"archived": "已封存",
|
||||
"done": "已完成",
|
||||
"in-progress": "進行中",
|
||||
"in-review": "審查中",
|
||||
"todo": "待辦",
|
||||
"triage": "規劃"
|
||||
},
|
||||
"delete": "刪除",
|
||||
"health": {
|
||||
"anomaly": {
|
||||
"duplicateActiveId": "",
|
||||
@@ -110,13 +93,6 @@
|
||||
"modelSetToDefault": ""
|
||||
}
|
||||
},
|
||||
"nodeStatus": {
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"offline": "",
|
||||
"online": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"nodes": {
|
||||
"auth": {
|
||||
"differ": "",
|
||||
@@ -137,7 +113,13 @@
|
||||
"stopped": ""
|
||||
}
|
||||
},
|
||||
"refresh": "重新整理",
|
||||
"nodeStatus": {
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"offline": "",
|
||||
"online": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"research": {
|
||||
"providerGitHub": "",
|
||||
"providerLlmSynthesis": "",
|
||||
@@ -145,7 +127,6 @@
|
||||
"providerPageFetch": "",
|
||||
"providerWebSearch": ""
|
||||
},
|
||||
"retry": "重試",
|
||||
"routing": {
|
||||
"policyLabel": {
|
||||
"block": "",
|
||||
@@ -205,7 +186,6 @@
|
||||
"zai": ""
|
||||
}
|
||||
},
|
||||
"skip": "略過",
|
||||
"taskForm": {
|
||||
"nodeStatusConnecting": "",
|
||||
"nodeStatusError": "",
|
||||
@@ -220,7 +200,6 @@
|
||||
"refreshSourceInitialLoad": "",
|
||||
"refreshSourceManual": ""
|
||||
},
|
||||
"tryAgain": "重試",
|
||||
"workflow": {
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
@@ -230,5 +209,14 @@
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
"summaryGateAdvisory": "",
|
||||
"summaryGateBlocks": "",
|
||||
"summaryHoldRelease": "",
|
||||
"summaryNotConfigured": "",
|
||||
"summaryReviewType": ""
|
||||
}
|
||||
}
|
||||
|
||||
42
packages/i18n/src/resources.d.ts
vendored
42
packages/i18n/src/resources.d.ts
vendored
@@ -6762,6 +6762,8 @@ export default interface Resources {
|
||||
"codeNote": "Runs sandboxed TypeScript. Syntax is validated at save.",
|
||||
"codeSource": "Source (TypeScript)",
|
||||
"codeTimeout": "Timeout (ms)",
|
||||
"cycleBlocked": "That connection would create a cycle — only rework edges inside a for-each template may loop back",
|
||||
"edgeCondition": "Condition",
|
||||
"edgeConditionLabel": "Condition: {{condition}}",
|
||||
"edgeInspector": "Edge",
|
||||
"edgeNoVerdict": "— success (no verdict) —",
|
||||
@@ -6783,6 +6785,7 @@ export default interface Resources {
|
||||
"foreachWorktree": "Per-step worktree",
|
||||
"gateBlocks": "Gate (blocks)",
|
||||
"gateMode": "Gate mode",
|
||||
"interpreterOnly": "This workflow branches, so it runs on the graph interpreter — it can't compile to the linear step engine, but it will still run.",
|
||||
"joinAll": "All branches",
|
||||
"joinAny": "Any branch",
|
||||
"joinMode": "Join mode",
|
||||
@@ -6803,14 +6806,7 @@ export default interface Resources {
|
||||
"reviewPlan": "Plan review",
|
||||
"reviewType": "Review type",
|
||||
"splitNote": "Branches run concurrently from this node. Execute and merge seams are not allowed inside a branch.",
|
||||
"stepExecuteLabel": "Step execute",
|
||||
"summaryAwaitInput": "Waits for user input",
|
||||
"summaryCodeDefault": "TypeScript",
|
||||
"summaryGateAdvisory": "Advisory",
|
||||
"summaryGateBlocks": "Gate (blocks)",
|
||||
"summaryHoldRelease": "Release: {{release}}",
|
||||
"summaryNotConfigured": "Not configured",
|
||||
"summaryReviewType": "{{type}} review"
|
||||
"stepExecuteLabel": "Step execute"
|
||||
},
|
||||
"workflowSelector": {
|
||||
"switchActiveMessage": "This task has an active session. Switching workflows aborts it and re-homes the card into the new workflow's entry column. Continue?",
|
||||
@@ -7070,11 +7066,6 @@ export default interface Resources {
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"save": "Save"
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "↓ Declining",
|
||||
@@ -7105,7 +7096,6 @@ export default interface Resources {
|
||||
"minutesAgo_other": "{{count}}m ago"
|
||||
}
|
||||
},
|
||||
"archive": "Archive",
|
||||
"board": {
|
||||
"rejection": {
|
||||
"capacityExhausted": "That column is at capacity. Try again when a slot frees up.",
|
||||
@@ -7115,7 +7105,6 @@ export default interface Resources {
|
||||
"workflowMismatch": "Drag can't move a card between workflows. Use the workflow switcher instead."
|
||||
}
|
||||
},
|
||||
"cancel": "Cancel",
|
||||
"chat": {
|
||||
"failedToGetResponse": "Failed to get response",
|
||||
"failureReferenceId": "ID",
|
||||
@@ -7134,16 +7123,6 @@ export default interface Resources {
|
||||
"toolCallsHeader": "Tool calls",
|
||||
"viewFailureDetails": "View failure details"
|
||||
},
|
||||
"close": "Close",
|
||||
"columns": {
|
||||
"archived": "Archived",
|
||||
"done": "Done",
|
||||
"in-progress": "In Progress",
|
||||
"in-review": "In Review",
|
||||
"todo": "Todo",
|
||||
"triage": "Planning"
|
||||
},
|
||||
"delete": "Delete",
|
||||
"health": {
|
||||
"anomaly": {
|
||||
"duplicateActiveId": "Duplicate active task ID",
|
||||
@@ -7208,7 +7187,6 @@ export default interface Resources {
|
||||
"stopped": "Stopped"
|
||||
}
|
||||
},
|
||||
"refresh": "Refresh",
|
||||
"research": {
|
||||
"providerGitHub": "GitHub",
|
||||
"providerLlmSynthesis": "LLM Synthesis",
|
||||
@@ -7216,7 +7194,6 @@ export default interface Resources {
|
||||
"providerPageFetch": "Page Fetch",
|
||||
"providerWebSearch": "Web Search"
|
||||
},
|
||||
"retry": "Retry",
|
||||
"routing": {
|
||||
"policyLabel": {
|
||||
"block": "Block execution",
|
||||
@@ -7276,7 +7253,6 @@ export default interface Resources {
|
||||
"zai": "GLM models by Zhipu AI — strong multilingual support"
|
||||
}
|
||||
},
|
||||
"skip": "Skip",
|
||||
"taskForm": {
|
||||
"nodeStatusConnecting": "Connecting",
|
||||
"nodeStatusError": "Error",
|
||||
@@ -7291,7 +7267,6 @@ export default interface Resources {
|
||||
"refreshSourceInitialLoad": "Initial load",
|
||||
"refreshSourceManual": "Manual"
|
||||
},
|
||||
"tryAgain": "Try Again",
|
||||
"workflow": {
|
||||
"postMerge": "Post-merge",
|
||||
"preMerge": "Pre-merge",
|
||||
@@ -7301,6 +7276,15 @@ export default interface Resources {
|
||||
"statusRunning": "Running…",
|
||||
"statusSkipped": "Skipped",
|
||||
"waitingForOutput": "Waiting for agent output…"
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "Waits for user input",
|
||||
"summaryCodeDefault": "TypeScript",
|
||||
"summaryGateAdvisory": "Advisory",
|
||||
"summaryGateBlocks": "Gate (blocks)",
|
||||
"summaryHoldRelease": "Release: {{release}}",
|
||||
"summaryNotConfigured": "Not configured",
|
||||
"summaryReviewType": "{{type}} review"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
|
||||
Reference in New Issue
Block a user