AI
Chat pointers, append-only agent instructions, turn hooks, and copy-to-chat for the AI sidebar.
Requires the ai permission. Registrations are activation-scoped: Harbor merges agentGuidance and hc.ai.instructions into the agent system prompt while the plugin is enabled and removes them on dispose or unload. Historical message badges keep working from persisted snapshots.
Plugins cannot rewrite Harbor's base system prompt — they only append fragments (via chat-pointer agentGuidance, instructions.add, or turn-scoped ctx.instructions.push).
Turn hooks (onBeforeTurn / onAfterTurn) fire once per user chat turn (not per LLM tool-loop step), and are unrelated to hc.http.onBeforeSend / onAfterSend.
hc.ai.copyToChat(input)
Available since v2.8.8
Opens the AI sidebar, ensures a chat exists, stores a snapshot, and queues the badge token in the composer.
Default grammar — pass key (host builds @plugin…):
Custom match — pass the full token including @:
Context longer than 100,000 characters is truncated with a clear marker. Pair with CopyToChatButton (or a CodeEditor copy-to-chat toolbar action) and call hc.ai.copyToChat from onSelect.
See Chat pointers for a full walkthrough.
await hc.ai.copyToChat({
pointerId: 'script',
key: scriptUuid,
label: scriptName,
context: scriptSource,
selection: { start: 0, end: 12 }
});await hc.ai.copyToChat({
pointerId: 'invoice',
token: '@invoice.inv-42#0.12',
label: 'Invoice inv-42',
context: invoiceText
});hc.ai.instructions.add(text)
Available since v2.0.0
Appends a static fragment to the agent system prompt while the returned disposable is active. Whitespace-only strings are ignored. hc.ai.instructions.list returns this plugin's currently registered fragments.
Merge order for the default agent path: Harbor base prompt → chat-pointer agentGuidance → static instructions.add fragments → (per turn) ephemeral system message from onBeforeTurn → ctx.instructions.push.
const handle = hc.ai.instructions.add(
'Prefer the WordPress MCP tools when the user asks about posts or pages.'
);
// later: handle.dispose();hc.ai.onAfterTurn(handler)
Available since v2.0.0
Runs once when the turn finishes (completed, cancelled, or error). The context is read-only: userMessage, assistantMessage, status, optional error, and stats (stepCount, toolCallCount, durationMs).
hc.ai.onBeforeTurn(handler)
Available since v2.0.0
Runs once when the user sends a chat message, before the first LLM completion step. The handler receives a mutable context:
hc.ai.onBeforeTurn((ctx) => {
ctx.instructions.push('The active invoice draft is INV-42.');
if (ctx.userMessage.content.includes('secret')) {
ctx.cancel('Blocked sensitive prompt.');
}
});hc.ai.registerChatPointer(config)
Available since v2.8.8
Default grammar (match / parse omitted) — tokens are @plugin.<pluginId>.<id>.<key> with an optional #start.end selection suffix:
Custom grammar — supply both match (body after @, as a RegExp or source string) and parse. Patterns that can match reserved builtin shapes (plugin, request, res, term, …) are rejected:
id must match [a-z][a-z0-9-]*. parse returns { key, selection? } or null; the host fills kind: 'plugin', pluginId, and token offsets. Composer highlighting uses a sync host fallback; your parse is authoritative at copy and send/validate over IPC.
hc.ai.registerChatPointer({
id: 'script',
agentGuidance:
'When a user message contains @plugin.<pluginId>.script.<key>, use the captured context in the system message.'
});hc.ai.registerChatPointer({
id: 'invoice',
match: /^invoice\.([A-Za-z0-9-]+)(?:#(\d+)\.(\d+))?/,
parse: (match) => {
const key = match[1];
if (key == null) return null;
return {
key,
selection: match[2] != null ? { start: Number(match[2]), end: Number(match[3]) } : undefined
};
},
agentGuidance: 'When @invoice.<id> appears, use the captured invoice context.'
});