Skip to content

Port Registrar: onboarding scaffolder to emit port-registrar-compliant templates#2368

Draft
TalZaccai wants to merge 2 commits into
mainfrom
talzacc/onboarding-scaffolder-migration
Draft

Port Registrar: onboarding scaffolder to emit port-registrar-compliant templates#2368
TalZaccai wants to merge 2 commits into
mainfrom
talzacc/onboarding-scaffolder-migration

Conversation

@TalZaccai
Copy link
Copy Markdown
Contributor

@TalZaccai TalZaccai commented May 19, 2026

Summary

Updates the onboarding scaffolder so newly-generated websocket-bridge and view-ui agents follow the modern port-allocation pattern introduced by the port-registrar work: OS-assigned port + context.registerPort(role, port), discoverable by external clients via discoverPort(name, role).

Templates emitted by pnpm onboarding scaffold were stuck on hardcoded ports (8081 / 9092) and a module-level singleton server, which collides with the dynamic-port contract every other agent has migrated to.

What changed

ts/packages/onboarding/src/scaffolder/scaffolderHandler.ts:

  • buildWebSocketBridgeTemplate (scaffoldPlugin): emits a class with a static start(port = 0) factory, close(), connected getter, and sendCommand helper. Wraps AgentWebSocketServer from websocket-utils so callers never bind a literal port.
  • buildWebSocketBridgeHandler (scaffoldAgent): full AppAgent with initializeAgentContext / updateAgentContext lifecycle, refcounted shared server, context.registerPort("bridge", port), and a backstop close().
  • buildViewUiHandler: full AppAgent with view server, context.registerPort("view", port), setLocalHostPort for shell integration, and an ActivityContext-driven openLocalView in executeAction.
  • Both templates honor <NAME>_BRIDGE_PORT / <NAME>_VIEW_PORT env-var overrides for debugging.
  • PLUGIN_TEMPLATES.nextSteps updated to show the new await <PascalName>Bridge.start() usage.

ts/docs/architecture/agent-patterns.md:

  • Sections 5 and 8 document the new port contract (registerPort / discoverPort, role name "default", env-var overrides).

Validation

  • pnpm --filter onboarding build
  • prettier --check
  • repo-policy-check
  • Manual scaffold smoke test: scaffolded a fresh websocket-bridge agent and a view-ui agent in a temp dir; both build clean, register their ports with the dispatcher on init, and are discoverable via discoverPort. Generated code matches the patterns established in already-migrated agents (e.g. code, localView).

Related

Updates the scaffolder so newly-generated websocket-bridge and view-ui agents follow the modern port-allocation pattern: OS-assigned port + context.registerPort(role, port), discoverable by external clients via discoverPort(name, role).

- buildWebSocketBridgeTemplate (scaffoldPlugin): static start(port=0) factory, close(), connected getter, sendCommand helper.
- buildWebSocketBridgeHandler (scaffoldAgent): full AppAgent lifecycle with refcounted shared server, registerPort, and backstop close.
- buildViewUiHandler: full AppAgent with view server, registerPort, setLocalHostPort for shell integration, and ActivityContext-driven openLocalView in executeAction.
- Both templates honor <NAME>_BRIDGE_PORT / <NAME>_VIEW_PORT env-var overrides for debugging.

- Updated PLUGIN_TEMPLATES nextSteps for websocket-bridge to reflect the new await <PascalName>Bridge.start() usage.
- agent-patterns.md sections 5 and 8 document the new port contract.

The office-addin template is left unchanged in this PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@TalZaccai TalZaccai requested a deployment to development-fork May 19, 2026 20:30 — with GitHub Actions Waiting
@TalZaccai TalZaccai requested a deployment to development-fork May 19, 2026 20:30 — with GitHub Actions Waiting
@TalZaccai TalZaccai requested a review from Copilot May 19, 2026 20:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates the onboarding scaffolder templates (websocket-bridge + view-ui) to follow the port-registrar contract: bind on OS-assigned ports by default and publish them via context.registerPort(...), with docs updated to describe the new contract.

Changes:

  • WebSocket bridge template now uses an async start(port=0) factory, tracks connections, and exposes .port for registrar publication.
  • WebSocket bridge agent template now manages a refcounted shared server, registers the bound port per session, and supports env-var port overrides.
  • View UI agent template now binds an HTTP server on port=0, registers/discloses ports, and surfaces the view via ActivityContext.openLocalView.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
ts/packages/agents/onboarding/src/scaffolder/scaffolderHandler.ts Updates scaffolded templates and next-steps text to use dynamic port binding + port registration patterns.
ts/docs/architecture/agent-patterns.md Documents the new register/discover port contract and env-var overrides for bridge + view patterns.
Comments suppressed due to low confidence (1)

ts/packages/agents/onboarding/src/scaffolder/scaffolderHandler.ts:1

  • isFirstForSession is computed before the await ensureSharedBridge(). If two updateAgentContext(true, ...) calls interleave for the same session (e.g. two schemas enabled concurrently), the second call can observe enabledSchemas.size > 0 and skip registration/refcounting, and then the first call can fail and roll back—leaving the session enabled with no portRegistration and no refcount increment. To make this robust, determine “first for session” at the time of registration (after the await) or gate registration on ctx.portRegistration (e.g., register iff ctx.portRegistration is undefined and ctx.enabledSchemas.size > 0), ideally under a per-session async mutex to prevent interleaving.
// Copyright (c) Microsoft Corporation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

defaultSubdir: "src",
nextSteps:
"Start the bridge with `new WebSocketBridge(port).start()` and connect your plugin.",
'Bind on an OS-assigned port via `await ${PascalName}Bridge.start()`, then publish the bound `.port` from your handler with `context.registerPort("default", bridge.port)` so external clients can discover it.',
Comment on lines 1564 to 1570
export function instantiate(): AppAgent {
return {
initializeAgentContext,
updateAgentContext,
closeAgentContext,
executeAction,
};
}
Comment on lines +1018 to +1019
server.on("error", () => {
/* TODO: log */
Comment on lines +1034 to +1042
public close(): Promise<void> {
for (const c of this.clients.values()) {
if (c.readyState === WebSocket.OPEN) c.close();
}
this.clients.clear();
return new Promise((resolve) =>
this.server.close(() => resolve()),
);
}
Comment on lines +1995 to +2001
if (activityContext) {
// ActivityContext is attached so the shell can open the view.
// The shape comes from the SDK; cast through unknown to keep
// the template free of internal-only ActionResult fields.
(result as unknown as { activityContext: ActivityContext }).activityContext =
activityContext;
}
Comment on lines +191 to +192
per session; `closeAgentContext` is the backstop that releases the
registration and closes the server if disable wasn't called.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants