* refactor: introduce provider plugin registry * refactor: move provider CLI to plugins * docs: add provider plugin implementation notes * refactor: shift provider runtime logic into plugins * refactor: add plugin defaults and summaries * docs: update provider plugin notes * feat(commands): add /commands slash list * Auto-reply: tidy help message * Auto-reply: fix status command lint * Tests: align google shared expectations * Auto-reply: tidy help message * Auto-reply: fix status command lint * refactor: move provider routing into plugins * test: align agent routing expectations * docs: update provider plugin notes * refactor: route replies via provider plugins * docs: note route-reply plugin hooks * refactor: extend provider plugin contract * refactor: derive provider status from plugins * refactor: unify gateway provider control * refactor: use plugin metadata in auto-reply * fix: parenthesize cron target selection * refactor: derive gateway methods from plugins * refactor: generalize provider logout * refactor: route provider logout through plugins * refactor: move WhatsApp web login methods into plugin * refactor: generalize provider log prefixes * refactor: centralize default chat provider * refactor: derive provider lists from registry * refactor: move provider reload noops into plugins * refactor: resolve web login provider via alias * refactor: derive CLI provider options from plugins * refactor: derive prompt provider list from plugins * style: apply biome lint fixes * fix: resolve provider routing edge cases * docs: update provider plugin refactor notes * fix(gateway): harden agent provider routing * refactor: move provider routing into plugins * refactor: move provider CLI to plugins * refactor: derive provider lists from registry * fix: restore slash command parsing * refactor: align provider ids for schema * refactor: unify outbound target resolution * fix: keep outbound labels stable * feat: add msteams to cron surfaces * fix: clean up lint build issues * refactor: localize chat provider alias normalization * refactor: drive gateway provider lists from plugins * docs: update provider plugin notes * style: format message-provider * fix: avoid provider registry init cycles * style: sort message-provider imports * fix: relax provider alias map typing * refactor: move provider routing into plugins * refactor: add plugin pairing/config adapters * refactor: route pairing and provider removal via plugins * refactor: align auto-reply provider typing * test: stabilize telegram media mocks * docs: update provider plugin refactor notes * refactor: pluginize outbound targets * refactor: pluginize provider selection * refactor: generalize text chunk limits * docs: update provider plugin notes * refactor: generalize group session/config * fix: normalize provider id for room detection * fix: avoid provider init in system prompt * style: formatting cleanup * refactor: normalize agent delivery targets * test: update outbound delivery labels * chore: fix lint regressions * refactor: extend provider plugin adapters * refactor: move elevated/block streaming defaults to plugins * refactor: defer outbound send deps to plugins * docs: note plugin-driven streaming/elevated defaults * refactor: centralize webchat provider constant * refactor: add provider setup adapters * refactor: delegate provider add config to plugins * docs: document plugin-driven provider add * refactor: add plugin state/binding metadata * refactor: build agent provider status from plugins * docs: note plugin-driven agent bindings * refactor: centralize internal provider constant usage * fix: normalize WhatsApp targets for groups and E.164 (#631) (thanks @imfing) * refactor: centralize default chat provider * refactor: centralize WhatsApp target normalization * refactor: move provider routing into plugins * refactor: normalize agent delivery targets * chore: fix lint regressions * fix: normalize WhatsApp targets for groups and E.164 (#631) (thanks @imfing) * feat: expand provider plugin adapters * refactor: route auto-reply via provider plugins * fix: align WhatsApp target normalization * fix: normalize WhatsApp targets for groups and E.164 (#631) (thanks @imfing) * refactor: centralize WhatsApp target normalization * feat: add /config chat config updates * docs: add /config get alias * feat(commands): add /commands slash list * refactor: centralize default chat provider * style: apply biome lint fixes * chore: fix lint regressions * fix: clean up whatsapp allowlist typing * style: format config command helpers * refactor: pluginize tool threading context * refactor: normalize session announce targets * docs: note new plugin threading and announce hooks * refactor: pluginize message actions * docs: update provider plugin actions notes * fix: align provider action adapters * refactor: centralize webchat checks * style: format message provider helpers * refactor: move provider onboarding into adapters * docs: note onboarding provider adapters * feat: add msteams onboarding adapter * style: organize onboarding imports * fix: normalize msteams allowFrom types * feat: add plugin text chunk limits * refactor: use plugin chunk limit fallbacks * feat: add provider mention stripping hooks * style: organize provider plugin type imports * refactor: generalize health snapshots * refactor: update macOS health snapshot handling * docs: refresh health snapshot notes * style: format health snapshot updates * refactor: drive security warnings via plugins * docs: note provider security adapter * style: format provider security adapters * refactor: centralize provider account defaults * refactor: type gateway client identity constants * chore: regen gateway protocol swift * fix: degrade health on failed provider probe * refactor: centralize pairing approve hint * docs: add plugin CLI command references * refactor: route auth and tool sends through plugins * docs: expand provider plugin hooks * refactor: document provider docking touchpoints * refactor: normalize internal provider defaults * refactor: streamline outbound delivery wiring * refactor: make provider onboarding plugin-owned * refactor: support provider-owned agent tools * refactor: move telegram draft chunking into telegram module * refactor: infer provider tool sends via extractToolSend * fix: repair plugin onboarding imports * refactor: de-dup outbound target normalization * style: tidy plugin and agent imports * refactor: data-drive provider selection line * fix: satisfy lint after provider plugin rebase * test: deflake gateway-cli coverage * style: format gateway-cli coverage test * refactor(provider-plugins): simplify provider ids * test(pairing-cli): avoid provider-specific ternary * style(macos): swiftformat HealthStore * refactor(sandbox): derive provider tool denylist * fix(sandbox): avoid plugin init in defaults * refactor(provider-plugins): centralize provider aliases * style(test): satisfy biome * refactor(protocol): v3 providers.status maps * refactor(ui): adapt to protocol v3 * refactor(macos): adapt to protocol v3 * test: update providers.status v3 fixtures * refactor(gateway): map provider runtime snapshot * test(gateway): update reload runtime snapshot * refactor(whatsapp): normalize heartbeat provider id * docs(refactor): update provider plugin notes * style: satisfy biome after rebase * fix: describe sandboxed elevated in prompt * feat(gateway): add agent image attachments + live probe * refactor: derive CLI provider options from plugins * fix(gateway): harden agent provider routing * fix(gateway): harden agent provider routing * refactor: align provider ids for schema * fix(protocol): keep agent provider string * fix(gateway): harden agent provider routing * fix(protocol): keep agent provider string * refactor: normalize agent delivery targets * refactor: support provider-owned agent tools * refactor(config): provider-keyed elevated allowFrom * style: satisfy biome * fix(gateway): appease provider narrowing * style: satisfy biome * refactor(reply): move group intro hints into plugin * fix(reply): avoid plugin registry init cycle * refactor(providers): add lightweight provider dock * refactor(gateway): use typed client id in connect * refactor(providers): document docks and avoid init cycles * refactor(providers): make media limit helper generic * fix(providers): break plugin registry import cycles * style: satisfy biome * refactor(status-all): build providers table from plugins * refactor(gateway): delegate web login to provider plugin * refactor(provider): drop web alias * refactor(provider): lazy-load monitors * style: satisfy lint/format * style: format status-all providers table * style: swiftformat gateway discovery model * test: make reload plan plugin-driven * fix: avoid token stringification in status-all * refactor: make provider IDs explicit in status * feat: warn on signal/imessage provider runtime errors * test: cover gateway provider runtime warnings in status * fix: add runtime kind to provider status issues * test: cover health degradation on probe failure * fix: keep routeReply lightweight * style: organize routeReply imports * refactor(web): extract auth-store helpers * refactor(whatsapp): lazy login imports * refactor(outbound): route replies via plugin outbound * docs: update provider plugin notes * style: format provider status issues * fix: make sandbox scope warning wrap-safe * refactor: load outbound adapters from provider plugins * docs: update provider plugin outbound notes * style(macos): fix swiftformat lint * docs: changelog for provider plugins * fix(macos): satisfy swiftformat * fix(macos): open settings via menu action * style: format after rebase * fix(macos): open Settings via menu action --------- Co-authored-by: LK <luke@kyohere.com> Co-authored-by: Luke K (pr-0f3t) <2609441+lc0rp@users.noreply.github.com> Co-authored-by: Xin <xin@imfing.com>
405 lines
16 KiB
TypeScript
405 lines
16 KiB
TypeScript
import type { ReasoningLevel, ThinkLevel } from "../auto-reply/thinking.js";
|
|
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
|
import { PROVIDER_IDS } from "../providers/registry.js";
|
|
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
|
|
|
|
const MESSAGE_PROVIDER_OPTIONS = PROVIDER_IDS.join("|");
|
|
|
|
export function buildAgentSystemPrompt(params: {
|
|
workspaceDir: string;
|
|
defaultThinkLevel?: ThinkLevel;
|
|
reasoningLevel?: ReasoningLevel;
|
|
extraSystemPrompt?: string;
|
|
ownerNumbers?: string[];
|
|
reasoningTagHint?: boolean;
|
|
toolNames?: string[];
|
|
toolSummaries?: Record<string, string>;
|
|
modelAliasLines?: string[];
|
|
userTimezone?: string;
|
|
userTime?: string;
|
|
contextFiles?: EmbeddedContextFile[];
|
|
skillsPrompt?: string;
|
|
heartbeatPrompt?: string;
|
|
runtimeInfo?: {
|
|
host?: string;
|
|
os?: string;
|
|
arch?: string;
|
|
node?: string;
|
|
model?: string;
|
|
provider?: string;
|
|
capabilities?: string[];
|
|
};
|
|
sandboxInfo?: {
|
|
enabled: boolean;
|
|
workspaceDir?: string;
|
|
workspaceAccess?: "none" | "ro" | "rw";
|
|
agentWorkspaceMount?: string;
|
|
browserControlUrl?: string;
|
|
browserNoVncUrl?: string;
|
|
hostBrowserAllowed?: boolean;
|
|
allowedControlUrls?: string[];
|
|
allowedControlHosts?: string[];
|
|
allowedControlPorts?: number[];
|
|
elevated?: {
|
|
allowed: boolean;
|
|
defaultLevel: "on" | "off";
|
|
};
|
|
};
|
|
}) {
|
|
const coreToolSummaries: Record<string, string> = {
|
|
read: "Read file contents",
|
|
write: "Create or overwrite files",
|
|
edit: "Make precise edits to files",
|
|
grep: "Search file contents for patterns",
|
|
find: "Find files by glob pattern",
|
|
ls: "List directory contents",
|
|
bash: "Run shell commands",
|
|
process: "Manage background bash sessions",
|
|
// Provider docking: add provider login tools here when a provider needs interactive linking.
|
|
browser: "Control web browser",
|
|
canvas: "Present/eval/snapshot the Canvas",
|
|
nodes: "List/describe/notify/camera/screen on paired nodes",
|
|
cron: "Manage cron jobs and wake events",
|
|
message: "Send messages and provider actions",
|
|
gateway:
|
|
"Restart, apply config, or run updates on the running Clawdbot process",
|
|
agents_list: "List agent ids allowed for sessions_spawn",
|
|
sessions_list: "List other sessions (incl. sub-agents) with filters/last",
|
|
sessions_history: "Fetch history for another session/sub-agent",
|
|
sessions_send: "Send a message to another session/sub-agent",
|
|
sessions_spawn: "Spawn a sub-agent session",
|
|
session_status:
|
|
"Show a /status-equivalent status card (usage/cost + Reasoning/Verbose/Elevated); optional per-session model override",
|
|
image: "Analyze an image with the configured image model",
|
|
};
|
|
|
|
const toolOrder = [
|
|
"read",
|
|
"write",
|
|
"edit",
|
|
"grep",
|
|
"find",
|
|
"ls",
|
|
"bash",
|
|
"process",
|
|
"browser",
|
|
"canvas",
|
|
"nodes",
|
|
"cron",
|
|
"message",
|
|
"gateway",
|
|
"agents_list",
|
|
"sessions_list",
|
|
"sessions_history",
|
|
"sessions_send",
|
|
"session_status",
|
|
"image",
|
|
];
|
|
|
|
const rawToolNames = (params.toolNames ?? []).map((tool) => tool.trim());
|
|
const canonicalToolNames = rawToolNames.filter(Boolean);
|
|
const canonicalByNormalized = new Map<string, string>();
|
|
for (const name of canonicalToolNames) {
|
|
const normalized = name.toLowerCase();
|
|
if (!canonicalByNormalized.has(normalized)) {
|
|
canonicalByNormalized.set(normalized, name);
|
|
}
|
|
}
|
|
const resolveToolName = (normalized: string) =>
|
|
canonicalByNormalized.get(normalized) ?? normalized;
|
|
|
|
const normalizedTools = canonicalToolNames.map((tool) => tool.toLowerCase());
|
|
const availableTools = new Set(normalizedTools);
|
|
const externalToolSummaries = new Map<string, string>();
|
|
for (const [key, value] of Object.entries(params.toolSummaries ?? {})) {
|
|
const normalized = key.trim().toLowerCase();
|
|
if (!normalized || !value?.trim()) continue;
|
|
externalToolSummaries.set(normalized, value.trim());
|
|
}
|
|
const extraTools = Array.from(
|
|
new Set(normalizedTools.filter((tool) => !toolOrder.includes(tool))),
|
|
);
|
|
const enabledTools = toolOrder.filter((tool) => availableTools.has(tool));
|
|
const toolLines = enabledTools.map((tool) => {
|
|
const summary = coreToolSummaries[tool] ?? externalToolSummaries.get(tool);
|
|
const name = resolveToolName(tool);
|
|
return summary ? `- ${name}: ${summary}` : `- ${name}`;
|
|
});
|
|
for (const tool of extraTools.sort()) {
|
|
const summary = coreToolSummaries[tool] ?? externalToolSummaries.get(tool);
|
|
const name = resolveToolName(tool);
|
|
toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);
|
|
}
|
|
|
|
const hasGateway = availableTools.has("gateway");
|
|
const readToolName = resolveToolName("read");
|
|
const bashToolName = resolveToolName("bash");
|
|
const processToolName = resolveToolName("process");
|
|
const extraSystemPrompt = params.extraSystemPrompt?.trim();
|
|
const ownerNumbers = (params.ownerNumbers ?? [])
|
|
.map((value) => value.trim())
|
|
.filter(Boolean);
|
|
const ownerLine =
|
|
ownerNumbers.length > 0
|
|
? `Owner numbers: ${ownerNumbers.join(", ")}. Treat messages from these numbers as the user.`
|
|
: undefined;
|
|
const reasoningHint = params.reasoningTagHint
|
|
? [
|
|
"ALL internal reasoning MUST be inside <think>...</think>.",
|
|
"Do not output any analysis outside <think>.",
|
|
"Format every reply as <think>...</think> then <final>...</final>, with no other text.",
|
|
"Only the final user-visible reply may appear inside <final>.",
|
|
"Only text inside <final> is shown to the user; everything else is discarded and never seen by the user.",
|
|
"Example:",
|
|
"<think>Short internal reasoning.</think>",
|
|
"<final>Hey there! What would you like to do next?</final>",
|
|
].join(" ")
|
|
: undefined;
|
|
const reasoningLevel = params.reasoningLevel ?? "off";
|
|
const userTimezone = params.userTimezone?.trim();
|
|
const userTime = params.userTime?.trim();
|
|
const skillsPrompt = params.skillsPrompt?.trim();
|
|
const heartbeatPrompt = params.heartbeatPrompt?.trim();
|
|
const heartbeatPromptLine = heartbeatPrompt
|
|
? `Heartbeat prompt: ${heartbeatPrompt}`
|
|
: "Heartbeat prompt: (configured)";
|
|
const runtimeInfo = params.runtimeInfo;
|
|
const runtimeProvider = runtimeInfo?.provider?.trim().toLowerCase();
|
|
const runtimeCapabilities = (runtimeInfo?.capabilities ?? [])
|
|
.map((cap) => String(cap).trim())
|
|
.filter(Boolean);
|
|
const runtimeCapabilitiesLower = new Set(
|
|
runtimeCapabilities.map((cap) => cap.toLowerCase()),
|
|
);
|
|
const inlineButtonsEnabled = runtimeCapabilitiesLower.has("inlinebuttons");
|
|
const skillsLines = skillsPrompt ? [skillsPrompt, ""] : [];
|
|
const skillsSection = skillsPrompt
|
|
? [
|
|
"## Skills",
|
|
`Skills provide task-specific instructions. Use \`${readToolName}\` to load the SKILL.md at the location listed for that skill.`,
|
|
...skillsLines,
|
|
"",
|
|
]
|
|
: [];
|
|
|
|
const lines = [
|
|
"You are a personal assistant running inside Clawdbot.",
|
|
"",
|
|
"## Tooling",
|
|
"Tool availability (filtered by policy):",
|
|
"Tool names are case-sensitive. Call tools exactly as listed.",
|
|
toolLines.length > 0
|
|
? toolLines.join("\n")
|
|
: [
|
|
"Pi lists the standard tools above. This runtime enables:",
|
|
"- grep: search file contents for patterns",
|
|
"- find: find files by glob pattern",
|
|
"- ls: list directory contents",
|
|
`- ${bashToolName}: run shell commands (supports background via yieldMs/background)`,
|
|
`- ${processToolName}: manage background bash sessions`,
|
|
"- browser: control clawd's dedicated browser",
|
|
"- canvas: present/eval/snapshot the Canvas",
|
|
"- nodes: list/describe/notify/camera/screen on paired nodes",
|
|
"- cron: manage cron jobs and wake events",
|
|
"- sessions_list: list sessions",
|
|
"- sessions_history: fetch session history",
|
|
"- sessions_send: send to another session",
|
|
].join("\n"),
|
|
"TOOLS.md does not control tool availability; it is user guidance for how to use external tools.",
|
|
"If a task is more complex or takes longer, spawn a sub-agent. It will do the work for you and ping you when it's done. You can always check up on it.",
|
|
"",
|
|
...skillsSection,
|
|
hasGateway ? "## Clawdbot Self-Update" : "",
|
|
hasGateway
|
|
? [
|
|
"Get Updates (self-update) is ONLY allowed when the user explicitly asks for it.",
|
|
"Do not run config.apply or update.run unless the user explicitly requests an update or config change; if it's not explicit, ask first.",
|
|
"Actions: config.get, config.schema, config.apply (validate + write full config, then restart), update.run (update deps or git, then restart).",
|
|
"After restart, Clawdbot pings the last active session automatically.",
|
|
].join("\n")
|
|
: "",
|
|
hasGateway ? "" : "",
|
|
"",
|
|
params.modelAliasLines && params.modelAliasLines.length > 0
|
|
? "## Model Aliases"
|
|
: "",
|
|
params.modelAliasLines && params.modelAliasLines.length > 0
|
|
? "Prefer aliases when specifying model overrides; full provider/model is also accepted."
|
|
: "",
|
|
params.modelAliasLines && params.modelAliasLines.length > 0
|
|
? params.modelAliasLines.join("\n")
|
|
: "",
|
|
params.modelAliasLines && params.modelAliasLines.length > 0 ? "" : "",
|
|
"## Workspace",
|
|
`Your working directory is: ${params.workspaceDir}`,
|
|
"Treat this directory as the single global workspace for file operations unless explicitly instructed otherwise.",
|
|
"",
|
|
params.sandboxInfo?.enabled ? "## Sandbox" : "",
|
|
params.sandboxInfo?.enabled
|
|
? [
|
|
"You are running in a sandboxed runtime (tools execute in Docker).",
|
|
"Some tools may be unavailable due to sandbox policy.",
|
|
"Sub-agents stay sandboxed (no elevated/host access). Need outside-sandbox read/write? Don't spawn; ask first.",
|
|
params.sandboxInfo.workspaceDir
|
|
? `Sandbox workspace: ${params.sandboxInfo.workspaceDir}`
|
|
: "",
|
|
params.sandboxInfo.workspaceAccess
|
|
? `Agent workspace access: ${params.sandboxInfo.workspaceAccess}${
|
|
params.sandboxInfo.agentWorkspaceMount
|
|
? ` (mounted at ${params.sandboxInfo.agentWorkspaceMount})`
|
|
: ""
|
|
}`
|
|
: "",
|
|
params.sandboxInfo.browserControlUrl
|
|
? `Sandbox browser control URL: ${params.sandboxInfo.browserControlUrl}`
|
|
: "",
|
|
params.sandboxInfo.browserNoVncUrl
|
|
? `Sandbox browser observer (noVNC): ${params.sandboxInfo.browserNoVncUrl}`
|
|
: "",
|
|
params.sandboxInfo.hostBrowserAllowed === true
|
|
? "Host browser control: allowed."
|
|
: params.sandboxInfo.hostBrowserAllowed === false
|
|
? "Host browser control: blocked."
|
|
: "",
|
|
params.sandboxInfo.allowedControlUrls?.length
|
|
? `Browser control URL allowlist: ${params.sandboxInfo.allowedControlUrls.join(
|
|
", ",
|
|
)}`
|
|
: "",
|
|
params.sandboxInfo.allowedControlHosts?.length
|
|
? `Browser control host allowlist: ${params.sandboxInfo.allowedControlHosts.join(
|
|
", ",
|
|
)}`
|
|
: "",
|
|
params.sandboxInfo.allowedControlPorts?.length
|
|
? `Browser control port allowlist: ${params.sandboxInfo.allowedControlPorts.join(
|
|
", ",
|
|
)}`
|
|
: "",
|
|
params.sandboxInfo.elevated?.allowed
|
|
? "Elevated bash is available for this session."
|
|
: "",
|
|
params.sandboxInfo.elevated?.allowed
|
|
? "User can toggle with /elevated on|off."
|
|
: "",
|
|
params.sandboxInfo.elevated?.allowed
|
|
? "You may also send /elevated on|off when needed."
|
|
: "",
|
|
params.sandboxInfo.elevated?.allowed
|
|
? `Current elevated level: ${
|
|
params.sandboxInfo.elevated.defaultLevel
|
|
} (on runs bash on host; off runs in sandbox).`
|
|
: "",
|
|
]
|
|
.filter(Boolean)
|
|
.join("\n")
|
|
: "",
|
|
params.sandboxInfo?.enabled ? "" : "",
|
|
ownerLine ? "## User Identity" : "",
|
|
ownerLine ?? "",
|
|
ownerLine ? "" : "",
|
|
"## Workspace Files (injected)",
|
|
"These user-editable files are loaded by Clawdbot and included below in Project Context.",
|
|
"",
|
|
userTimezone || userTime
|
|
? `Time: assume UTC unless stated. User TZ=${userTimezone ?? "unknown"}. Current user time (converted)=${userTime ?? "unknown"}.`
|
|
: "",
|
|
userTimezone || userTime ? "" : "",
|
|
"## Reply Tags",
|
|
"To request a native reply/quote on supported surfaces, include one tag in your reply:",
|
|
"- [[reply_to_current]] replies to the triggering message.",
|
|
"- [[reply_to:<id>]] replies to a specific message id when you have it.",
|
|
"Whitespace inside the tag is allowed (e.g. [[ reply_to_current ]] / [[ reply_to: 123 ]]).",
|
|
"Tags are stripped before sending; support depends on the current provider config.",
|
|
"",
|
|
"## Messaging",
|
|
"- Reply in current session → automatically routes to the source provider (Signal, Telegram, etc.)",
|
|
"- Cross-session messaging → use sessions_send(sessionKey, message)",
|
|
"- Never use bash/curl for provider messaging; Clawdbot handles all routing internally.",
|
|
availableTools.has("message")
|
|
? [
|
|
"",
|
|
"### message tool",
|
|
"- Use `message` for proactive sends + provider actions (polls, reactions, etc.).",
|
|
"- For `action=send`, include `to` and `message`.",
|
|
`- If multiple providers are configured, pass \`provider\` (${MESSAGE_PROVIDER_OPTIONS}).`,
|
|
inlineButtonsEnabled
|
|
? "- Inline buttons supported. Use `action=send` with `buttons=[[{text,callback_data}]]` (callback_data routes back as a user message)."
|
|
: runtimeProvider
|
|
? `- Inline buttons not enabled for ${runtimeProvider}. If you need them, ask to add "inlineButtons" to ${runtimeProvider}.capabilities or ${runtimeProvider}.accounts.<id>.capabilities.`
|
|
: "",
|
|
]
|
|
.filter(Boolean)
|
|
.join("\n")
|
|
: "",
|
|
"",
|
|
];
|
|
|
|
if (extraSystemPrompt) {
|
|
lines.push("## Group Chat Context", extraSystemPrompt, "");
|
|
}
|
|
if (reasoningHint) {
|
|
lines.push("## Reasoning Format", reasoningHint, "");
|
|
}
|
|
|
|
const contextFiles = params.contextFiles ?? [];
|
|
if (contextFiles.length > 0) {
|
|
lines.push(
|
|
"# Project Context",
|
|
"",
|
|
"The following project context files have been loaded:",
|
|
"",
|
|
);
|
|
for (const file of contextFiles) {
|
|
lines.push(`## ${file.path}`, "", file.content, "");
|
|
}
|
|
}
|
|
|
|
lines.push(
|
|
"## Silent Replies",
|
|
`When you have nothing to say, respond with ONLY: ${SILENT_REPLY_TOKEN}`,
|
|
"",
|
|
"⚠️ Rules:",
|
|
"- It must be your ENTIRE message — nothing else",
|
|
`- Never append it to an actual response (never include "${SILENT_REPLY_TOKEN}" in real replies)`,
|
|
"- Never wrap it in markdown or code blocks",
|
|
"",
|
|
`❌ Wrong: "Here's help... ${SILENT_REPLY_TOKEN}"`,
|
|
`❌ Wrong: "${SILENT_REPLY_TOKEN}"`,
|
|
`✅ Right: ${SILENT_REPLY_TOKEN}`,
|
|
"",
|
|
"## Heartbeats",
|
|
heartbeatPromptLine,
|
|
"If you receive a heartbeat poll (a user message matching the heartbeat prompt above), and there is nothing that needs attention, reply exactly:",
|
|
"HEARTBEAT_OK",
|
|
'Clawdbot treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
|
|
'If something needs attention, do NOT include "HEARTBEAT_OK"; reply with the alert text instead.',
|
|
"",
|
|
"## Runtime",
|
|
`Runtime: ${[
|
|
runtimeInfo?.host ? `host=${runtimeInfo.host}` : "",
|
|
runtimeInfo?.os
|
|
? `os=${runtimeInfo.os}${runtimeInfo?.arch ? ` (${runtimeInfo.arch})` : ""}`
|
|
: runtimeInfo?.arch
|
|
? `arch=${runtimeInfo.arch}`
|
|
: "",
|
|
runtimeInfo?.node ? `node=${runtimeInfo.node}` : "",
|
|
runtimeInfo?.model ? `model=${runtimeInfo.model}` : "",
|
|
runtimeProvider ? `provider=${runtimeProvider}` : "",
|
|
runtimeProvider
|
|
? `capabilities=${
|
|
runtimeCapabilities.length > 0
|
|
? runtimeCapabilities.join(",")
|
|
: "none"
|
|
}`
|
|
: "",
|
|
`thinking=${params.defaultThinkLevel ?? "off"}`,
|
|
]
|
|
.filter(Boolean)
|
|
.join(" | ")}`,
|
|
`Reasoning: ${reasoningLevel} (hidden unless on/stream). Toggle /reasoning; /status shows Reasoning when enabled.`,
|
|
);
|
|
|
|
return lines.filter(Boolean).join("\n");
|
|
}
|