mirror of
https://github.com/github/awesome-copilot.git
synced 2026-07-14 18:11:01 +00:00
123 lines
5.4 KiB
JavaScript
123 lines
5.4 KiB
JavaScript
// extension.mjs — Java Modernization Studio (user-scoped canvas). Wiring only.
|
|
//
|
|
// Declares the canvas and bridges the SDK to the testable modules:
|
|
// scan.mjs repo + markdown parsing -> grounded state
|
|
// catalog.mjs Microsoft predefined task catalog
|
|
// prompts.mjs action -> crafted agent prompt
|
|
// server.mjs per-instance loopback HTTP server + action dispatch
|
|
// renderer.mjs cockpit UI
|
|
//
|
|
// Button clicks POST /action; agent actions are forwarded to session.send. When a
|
|
// turn finishes (session.idle) every open cockpit re-scans and pushes fresh state.
|
|
|
|
import { joinSession, createCanvas, CanvasError } from "@github/copilot-sdk/extension";
|
|
import { basename } from "node:path";
|
|
import { scanRepo } from "./scan.mjs";
|
|
import { createInstanceServer, pushState, resolveRepoPath } from "./server.mjs";
|
|
import { AUTOPILOT_TURN_TIMEOUT_MS } from "./autopilot.mjs";
|
|
|
|
const instances = new Map(); // instanceId -> rec { server, url, repoPath, sseClients }
|
|
let sessionRef = null;
|
|
let lastWorkingDir = null;
|
|
|
|
const session = await joinSession({
|
|
canvases: [
|
|
createCanvas({
|
|
id: "appmod-cockpit",
|
|
displayName: "Java Modernization Studio",
|
|
description:
|
|
"Drive the GitHub Copilot App Modernization for Java workflow: assessment, plan/progress, validation gates, and task/skill runs grounded in the repo's real artifacts.",
|
|
inputSchema: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
properties: {
|
|
repoPath: { type: "string", description: "Absolute path to the Java repo to inspect." },
|
|
initialTab: { type: "string", enum: ["overview", "readiness", "assessment", "plan", "validation", "tasks", "summary"] },
|
|
},
|
|
},
|
|
actions: [
|
|
{
|
|
name: "get_state",
|
|
description:
|
|
"Return the current modernization state snapshot scanned from the repo (assessment, plan/progress, gates, skills, tasks).",
|
|
handler: async (ctx) => {
|
|
const rec = instances.get(ctx.instanceId);
|
|
const repoPath = rec ? rec.repoPath : resolveRepoPath(ctx, lastWorkingDir);
|
|
return await scanRepo(repoPath);
|
|
},
|
|
},
|
|
{
|
|
name: "refresh",
|
|
description: "Re-scan the repo and push a fresh snapshot to the open cockpit.",
|
|
handler: async (ctx) => {
|
|
const rec = instances.get(ctx.instanceId);
|
|
if (!rec) throw new CanvasError("not_open", "Cockpit instance is not open.");
|
|
await pushState(rec, logFn);
|
|
return { ok: true, repoPath: rec.repoPath };
|
|
},
|
|
},
|
|
],
|
|
open: async (ctx) => {
|
|
const repoPath = resolveRepoPath(ctx, lastWorkingDir);
|
|
if (repoPath) lastWorkingDir = repoPath; // never clobber a good target with null
|
|
const initialTab = ctx.input && ctx.input.initialTab ? ctx.input.initialTab : "overview";
|
|
let rec = instances.get(ctx.instanceId);
|
|
if (!rec) {
|
|
rec = await createInstanceServer({
|
|
instanceId: ctx.instanceId,
|
|
repoPath,
|
|
initialTab,
|
|
sendPrompt: (prompt) => sessionRef.send({ prompt }),
|
|
runTurn: (prompt) => sessionRef.sendAndWait({ prompt }, AUTOPILOT_TURN_TIMEOUT_MS),
|
|
log: logFn,
|
|
});
|
|
instances.set(ctx.instanceId, rec);
|
|
} else if (rec.repoPath !== repoPath) {
|
|
rec.repoPath = repoPath; // refresh target on re-open
|
|
rec.doctor = null; // env readiness depends on repo facts — recompute for the new target
|
|
}
|
|
return {
|
|
title: "Java Modernization Studio",
|
|
status: repoPath ? basename(repoPath) : "no repo",
|
|
url: rec.url,
|
|
};
|
|
},
|
|
onClose: async (ctx) => {
|
|
const rec = instances.get(ctx.instanceId);
|
|
if (rec) {
|
|
instances.delete(ctx.instanceId);
|
|
for (const res of rec.sseClients) {
|
|
try {
|
|
res.end();
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
await new Promise((resolve) => rec.server.close(() => resolve()));
|
|
}
|
|
},
|
|
}),
|
|
],
|
|
});
|
|
|
|
sessionRef = session;
|
|
function logFn(message, opts) {
|
|
try {
|
|
return sessionRef.log(message, opts);
|
|
} catch {
|
|
/* logging is best-effort */
|
|
}
|
|
}
|
|
|
|
if (session.workspacePath && !lastWorkingDir) lastWorkingDir = session.workspacePath;
|
|
|
|
// A finished turn may have changed plan.md/progress.md/summary.md or the working
|
|
// tree — re-scan and push fresh state to every open cockpit.
|
|
session.on("session.idle", async () => {
|
|
for (const rec of instances.values()) {
|
|
await pushState(rec, logFn);
|
|
}
|
|
});
|
|
|
|
await session.log("Java Modernization Studio ready", { ephemeral: true });
|