HarborClient’s AI sidebar already knows collections, requests, scripts, and more. Plugin authors can now push extra policy and turn-time context into that agent without replacing Harbor’s base system prompt—using the Plugin SDK’s hc.ai surface.
If you need badged @ mentions that snapshot editor context into chat, see Register Chat Pointers in the HarborClient Plugin SDK. This post focuses on instructions.add, onBeforeTurn, and onAfterTurn, then walks through a small SSE Export plugin that teaches the agent how to talk about Server-Sent Events.
What hc.ai gives you
Declare the ai permission in your manifest. Registrations are activation-scoped: they apply while the plugin is enabled and disappear on dispose or unload.
hc.ai.instructions.add(text)— append a static fragment to the agent system prompt; returns aDisposable. Read this plugin’s fragments viahc.ai.instructions.list.hc.ai.onBeforeTurn(handler)— runs once when the user sends a chat message, before the first LLM step. You can push turn-only instructions, rewrite model-facing user text, orcancel(reason).hc.ai.onAfterTurn(handler)— runs once when the turn finishes (completed,cancelled, orerror), with assistant text and cheapstats.registerChatPointer/copyToChat—@badges and snapshots (covered in the sibling post).
Important limits: plugins cannot rewrite Harbor’s base system prompt—only append. Turn hooks fire once per user chat turn, not once per tool-loop step. They are unrelated to hc.http.onBeforeSend / onAfterSend.
Static instructions vs turn hooks
Use static instructions for always-on policy (“when the user asks about X, prefer Y”). Use before-turn hooks for ephemeral context that only matters for this send—active draft ids, feature flags, or a reminder tied to the open request.
Merge order for the default agent path is roughly: Harbor base prompt → chat-pointer agentGuidance → static instructions.add fragments → ephemeral system text from onBeforeTurn → ctx.instructions.push.
export function activate(hc) {
hc.ai.instructions.add(
'Prefer Invoice MCP tools when the user asks about billing fields.'
);
hc.ai.onBeforeTurn((ctx) => {
ctx.instructions.push(`Active draft: ${getActiveDraftId()}`);
if (ctx.userMessage.content.includes('secret')) {
ctx.cancel('Blocked sensitive prompt.');
}
});
hc.ai.onAfterTurn((ctx) => {
if (ctx.status === 'error') {
console.error(ctx.error?.message);
}
});
}
Build an “SSE Export” plugin
HarborClient treats SSE as a first-class request protocol: pick SSE in the method dropdown, connect a long-lived stream, and inspect events in the response Events and Raw tabs. The sample below does not invent new agent tools. It teaches the chat agent how to talk about SSE—and demonstrates both static instructions and turn hooks.
Manifest
{
"id": "com.example.sse-export",
"name": "SSE Export",
"version": "1.0.0",
"summary": "Teach the AI agent how to reason about SSE streams.",
"engines": { "harborclient": ">=2.6.0" },
"renderer": "dist/renderer.js",
"permissions": ["ai", "ui"]
}
ui is only needed here for hc.ui.showToast in the after-turn hook. Drop it if you only log to the console.
Always-on SSE policy
const SSE_AGENT_RULES = `
When the user asks about Server-Sent Events (SSE) in HarborClient:
1. Treat SSE as a long-lived event stream, not a single buffered HTTP body.
2. Prefer the Events viewer (and Raw) over inventing event payloads.
3. Describe event name, data, id, and reconnect behavior carefully; do not claim the stream finished unless the UI shows it closed.
4. Do not tell the user to paste stream dumps when the open SSE tab already shows events.
`.trim();
export function activate(hc) {
hc.ai.instructions.add(SSE_AGENT_RULES);
// hooks below…
}
Before turn: push stream-aware context
When the user’s message looks SSE-related, add a short ephemeral reminder for this turn only. You can also rewrite model-facing text (for example stripping a slash-command prefix) without changing the persisted chat row.
function looksLikeSseQuestion(text) {
return /\b(sse|server[- ]?sent|event[- ]?stream|events tab|reconnect)\b/i.test(
text
);
}
hc.ai.onBeforeTurn((ctx) => {
let content = ctx.userMessage.content;
if (content.startsWith('/sse ')) {
content = content.slice('/sse '.length);
ctx.userMessage.content = content;
}
if (!looksLikeSseQuestion(content)) {
return;
}
ctx.instructions.push(
'User is asking about SSE. Prefer Events/Raw on the open SSE request tab; do not invent stream payloads.'
);
});
After turn: observe (and stub an export)
The after-turn hook is a good place for telemetry or a future “write JSON export” path. This stub toasts success stats and surfaces errors—wire filesystem:write later if you want a real file export.
hc.ai.onAfterTurn((ctx) => {
if (ctx.status === 'error') {
hc.ui.showToast(`AI turn failed: ${ctx.error?.message ?? 'unknown error'}`);
return;
}
if (ctx.status !== 'completed') {
return;
}
// Stub: persist ctx.assistantMessage / SSE event snapshots here later.
const { stepCount, toolCallCount, durationMs } = ctx.stats;
hc.ui.showToast(
`SSE Export: turn done (${stepCount} steps, ${toolCallCount} tools, ${durationMs} ms)`
);
});
Putting it together
const SSE_AGENT_RULES = `When the user asks about SSE in HarborClient: treat it as a long-lived stream; prefer Events/Raw; do not invent payloads.`.trim();
function looksLikeSseQuestion(text) {
return /\b(sse|server[- ]?sent|event[- ]?stream|events tab|reconnect)\b/i.test(text);
}
export function activate(hc) {
hc.ai.instructions.add(SSE_AGENT_RULES);
hc.ai.onBeforeTurn((ctx) => {
if (ctx.userMessage.content.startsWith('/sse ')) {
ctx.userMessage.content = ctx.userMessage.content.slice('/sse '.length);
}
if (looksLikeSseQuestion(ctx.userMessage.content)) {
ctx.instructions.push(
'User is asking about SSE. Prefer Events/Raw on the open SSE tab.'
);
}
});
hc.ai.onAfterTurn((ctx) => {
if (ctx.status === 'error') {
hc.ui.showToast(`AI turn failed: ${ctx.error?.message ?? 'unknown error'}`);
return;
}
if (ctx.status === 'completed') {
const { stepCount, toolCallCount, durationMs } = ctx.stats;
hc.ui.showToast(
`SSE Export: turn done (${stepCount} steps, ${toolCallCount} tools, ${durationMs} ms)`
);
}
});
}
Try it
Load an unpacked plugin with the ai permission, open an SSE request, and ask the agent how to read the Events tab. For the full API surface, see the SDK hc.ai reference and the AI instructions and turn hooks example. Grab the latest HarborClient from harborclient.com or GitHub Releases.






Leave a Reply