mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
feat(dashboard): fromCloudAgent orchestration mapper
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/** Cloud Agent tasks → unified orchestration nodes. Pure. */
|
||||
import type { CloudAgentTask } from "@/lib/cloudAgent/types";
|
||||
import type { OrchEdge, OrchNode, OrchState } from "./orchestrationTypes";
|
||||
|
||||
const STATE_MAP: Record<string, OrchState> = {
|
||||
queued: "queued",
|
||||
running: "running",
|
||||
awaiting_approval: "waiting_approval",
|
||||
completed: "succeeded",
|
||||
failed: "failed",
|
||||
cancelled: "cancelled",
|
||||
};
|
||||
|
||||
function truncate(s: string, n = 60): string {
|
||||
return s.length > n ? `${s.slice(0, n - 1)}…` : s;
|
||||
}
|
||||
|
||||
export function fromCloudAgent(tasks: CloudAgentTask[]): { nodes: OrchNode[]; edges: OrchEdge[] } {
|
||||
if (tasks.length === 0) return { nodes: [], edges: [] };
|
||||
const nodes: OrchNode[] = [];
|
||||
const edges: OrchEdge[] = [];
|
||||
const counts: Partial<Record<OrchState, number>> = {};
|
||||
|
||||
for (const t of tasks) {
|
||||
const mapped = STATE_MAP[t.status];
|
||||
const state: OrchState = mapped ?? "failed";
|
||||
counts[state] = (counts[state] ?? 0) + 1;
|
||||
const id = `cloud-agent:${t.id}`;
|
||||
const active = state === "running";
|
||||
nodes.push({
|
||||
id,
|
||||
kind: "work",
|
||||
source: "cloud-agent",
|
||||
state,
|
||||
label: truncate(t.prompt),
|
||||
sublabel: mapped ? t.providerId : `${t.providerId} — unknown status: ${String(t.status)}`,
|
||||
startedAt: t.createdAt,
|
||||
updatedAt: t.updatedAt,
|
||||
endedAt: t.completedAt,
|
||||
cost: t.result?.cost,
|
||||
raw: t,
|
||||
});
|
||||
edges.push({
|
||||
id: `e:source:cloud-agent→${id}`,
|
||||
from: "source:cloud-agent",
|
||||
to: id,
|
||||
kind: "owns",
|
||||
active,
|
||||
});
|
||||
|
||||
const last = t.activities[t.activities.length - 1];
|
||||
if (active && last) {
|
||||
const actId = `${id}:activity`;
|
||||
nodes.push({
|
||||
id: actId,
|
||||
kind: "activity",
|
||||
source: "cloud-agent",
|
||||
state,
|
||||
label: truncate(last.content),
|
||||
sublabel: last.type,
|
||||
updatedAt: last.timestamp,
|
||||
});
|
||||
edges.push({ id: `e:${id}→${actId}`, from: id, to: actId, kind: "owns", active: true });
|
||||
}
|
||||
}
|
||||
|
||||
nodes.unshift({
|
||||
id: "source:cloud-agent",
|
||||
kind: "source",
|
||||
source: "cloud-agent",
|
||||
label: "Cloud Agent",
|
||||
counts,
|
||||
});
|
||||
return { nodes, edges };
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
orchStateColor,
|
||||
} from "../../../src/app/(dashboard)/dashboard/orchestration/model/orchestrationTypes.ts";
|
||||
import { STATUS_HEX } from "../../../src/shared/constants/statusColors.ts";
|
||||
import { fromCloudAgent } from "../../../src/app/(dashboard)/dashboard/orchestration/model/fromCloudAgent.ts";
|
||||
import type { CloudAgentTask } from "../../../src/lib/cloudAgent/types.ts";
|
||||
|
||||
describe("orchestrationTypes", () => {
|
||||
it("covers all six states with a color each", () => {
|
||||
@@ -27,3 +29,75 @@ describe("orchestrationTypes", () => {
|
||||
assert.equal(orchStateColor("failed"), STATUS_HEX.error);
|
||||
});
|
||||
});
|
||||
|
||||
function caTask(over: Partial<CloudAgentTask>): CloudAgentTask {
|
||||
return {
|
||||
id: "t1",
|
||||
providerId: "devin",
|
||||
status: "running",
|
||||
prompt: "Fix the flaky test in CI",
|
||||
source: { repoName: "acme/app", repoUrl: "https://github.com/acme/app" },
|
||||
options: {},
|
||||
activities: [],
|
||||
createdAt: "2026-08-30T10:00:00Z",
|
||||
updatedAt: "2026-08-30T10:05:00Z",
|
||||
...over,
|
||||
} as CloudAgentTask;
|
||||
}
|
||||
|
||||
describe("fromCloudAgent", () => {
|
||||
it("maps every status to the unified OrchState", () => {
|
||||
const cases: Array<[CloudAgentTask["status"], string]> = [
|
||||
["queued", "queued"],
|
||||
["running", "running"],
|
||||
["awaiting_approval", "waiting_approval"],
|
||||
["completed", "succeeded"],
|
||||
["failed", "failed"],
|
||||
["cancelled", "cancelled"],
|
||||
];
|
||||
for (const [input, expected] of cases) {
|
||||
const { nodes } = fromCloudAgent([caTask({ status: input })]);
|
||||
const work = nodes.find((n) => n.kind === "work");
|
||||
assert.equal(work?.state, expected, input);
|
||||
}
|
||||
});
|
||||
it("unknown status becomes failed with the raw value in sublabel", () => {
|
||||
const { nodes } = fromCloudAgent([caTask({ status: "exploded" as CloudAgentTask["status"] })]);
|
||||
const work = nodes.find((n) => n.kind === "work");
|
||||
assert.equal(work?.state, "failed");
|
||||
assert.match(work?.sublabel ?? "", /exploded/);
|
||||
});
|
||||
it("running task with activities gets one ActivityNode; completed does not", () => {
|
||||
const running = caTask({
|
||||
activities: [
|
||||
{ id: "a1", type: "command", content: "npm test", timestamp: "2026-08-30T10:04:00Z" },
|
||||
],
|
||||
});
|
||||
const done = caTask({ id: "t2", status: "completed", activities: running.activities });
|
||||
const { nodes, edges } = fromCloudAgent([running, done]);
|
||||
const acts = nodes.filter((n) => n.kind === "activity");
|
||||
assert.equal(acts.length, 1);
|
||||
assert.equal(acts[0].id, "cloud-agent:t1:activity");
|
||||
assert.ok(
|
||||
edges.some(
|
||||
(e) => e.from === "cloud-agent:t1" && e.to === "cloud-agent:t1:activity" && e.active
|
||||
)
|
||||
);
|
||||
});
|
||||
it("emits a SourceNode with per-state counts and owns-edges from it", () => {
|
||||
const { nodes, edges } = fromCloudAgent([caTask({}), caTask({ id: "t2", status: "failed" })]);
|
||||
const src = nodes.find((n) => n.id === "source:cloud-agent");
|
||||
assert.equal(src?.counts?.running, 1);
|
||||
assert.equal(src?.counts?.failed, 1);
|
||||
assert.ok(
|
||||
edges.some(
|
||||
(e) => e.from === "source:cloud-agent" && e.to === "cloud-agent:t1" && e.kind === "owns"
|
||||
)
|
||||
);
|
||||
});
|
||||
it("empty input emits nothing", () => {
|
||||
const out = fromCloudAgent([]);
|
||||
assert.equal(out.nodes.length, 0);
|
||||
assert.equal(out.edges.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user