Skip to content

Commit a1b4104

Browse files
Scaffold workflow fleet canvas
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent f296f37 commit a1b4104

1 file changed

Lines changed: 89 additions & 0 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Extension: process-workflow-fleet
2+
// Inspect Process-PSModule caller workflows and prepare safe v8 migrations.
3+
//
4+
// This single-file skeleton is a starting point. For more complex canvases
5+
// (multiple actions with non-trivial logic, shared state, a custom renderer,
6+
// etc.) prefer splitting things out: move each action handler into its own
7+
// function, extract `open`/`onClose` into helpers, and pull large units
8+
// (renderer assets, schema definitions, shared utilities) into sibling files
9+
// imported from this entry point. Keep extension.mjs focused on wiring.
10+
11+
import { createServer } from "node:http";
12+
import { joinSession, createCanvas } from "@github/copilot-sdk/extension";
13+
14+
// One local HTTP server per open canvas instance. Each instance gets its own
15+
// ephemeral port so multiple canvases (or multiple opens of the same canvas)
16+
// don't collide. Replace this with your real renderer — point a static-file
17+
// server, a Vite/Next dev server, or any framework you like at the same URL.
18+
const servers = new Map();
19+
20+
function renderHtml(instanceId) {
21+
return `<!doctype html>
22+
<html>
23+
<head><meta charset="utf-8" /><title>process-workflow-fleet</title></head>
24+
<body style="font-family: system-ui; padding: 1rem;">
25+
<h1>process-workflow-fleet</h1>
26+
<p>Hello from a local canvas server.</p>
27+
<p>Instance: <code>${instanceId}</code></p>
28+
</body>
29+
</html>`;
30+
}
31+
32+
async function startServer(instanceId) {
33+
const server = createServer((req, res) => {
34+
res.setHeader("Content-Type", "text/html; charset=utf-8");
35+
res.end(renderHtml(instanceId));
36+
});
37+
// Port 0 = let the OS pick a free ephemeral port. Bind to loopback only.
38+
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
39+
const address = server.address();
40+
const port = typeof address === "object" && address ? address.port : 0;
41+
return { server, url: `http://127.0.0.1:${port}/` };
42+
}
43+
44+
const session = await joinSession({
45+
canvases: [
46+
createCanvas({
47+
id: "process-workflow-fleet",
48+
displayName: "process-workflow-fleet",
49+
description: "Example canvas - replace with your implementation",
50+
// Optional JSON Schema describing the input passed to open():
51+
// inputSchema: { type: "object", properties: {} },
52+
actions: [
53+
{
54+
name: "example_action",
55+
description: "Example agent-callable action on this canvas",
56+
// Optional JSON Schema for the action input:
57+
// inputSchema: { type: "object", properties: {} },
58+
handler: async (ctx) => {
59+
return { ok: true, instanceId: ctx.instanceId };
60+
},
61+
},
62+
],
63+
// Called when the agent or host opens the canvas. We boot a local
64+
// HTTP server on an ephemeral port and hand its URL back to the
65+
// host so it can render the canvas. Re-opens with the same
66+
// instanceId reuse the existing server.
67+
open: async (ctx) => {
68+
let entry = servers.get(ctx.instanceId);
69+
if (!entry) {
70+
entry = await startServer(ctx.instanceId);
71+
servers.set(ctx.instanceId, entry);
72+
}
73+
return {
74+
title: "process-workflow-fleet",
75+
url: entry.url,
76+
};
77+
},
78+
// Tear the per-instance server down when the canvas is closed so
79+
// ports are not leaked across the lifetime of the extension.
80+
onClose: async (ctx) => {
81+
const entry = servers.get(ctx.instanceId);
82+
if (entry) {
83+
servers.delete(ctx.instanceId);
84+
await new Promise((resolve) => entry.server.close(() => resolve()));
85+
}
86+
},
87+
}),
88+
],
89+
});

0 commit comments

Comments
 (0)