Add daemon-wide system prompts (#1100)
* Add daemon-wide system prompt setting * Align settings textarea rendering * Polish daemon settings layout * Remove orchestrator prompt injection
This commit is contained in:
@@ -100,12 +100,15 @@ function expectArchivedAgentRecord(
|
||||
class TestAgentClient implements AgentClient {
|
||||
readonly provider = "codex" as const;
|
||||
readonly capabilities = TEST_CAPABILITIES;
|
||||
readonly createdConfigs: AgentSessionConfig[] = [];
|
||||
readonly resumeOverrides: Array<Partial<AgentSessionConfig> | undefined> = [];
|
||||
|
||||
async isAvailable(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async createSession(config: AgentSessionConfig): Promise<AgentSession> {
|
||||
this.createdConfigs.push(config);
|
||||
return new TestAgentSession(config);
|
||||
}
|
||||
|
||||
@@ -135,9 +138,11 @@ class TestAgentClient implements AgentClient {
|
||||
config?: Partial<AgentSessionConfig>,
|
||||
_launchContext?: AgentLaunchContext,
|
||||
): Promise<AgentSession> {
|
||||
this.resumeOverrides.push(config);
|
||||
return new TestAgentSession({
|
||||
provider: "codex",
|
||||
cwd: config?.cwd ?? process.cwd(),
|
||||
daemonAppendSystemPrompt: config?.daemonAppendSystemPrompt,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -391,6 +396,62 @@ test("normalizeConfig strips legacy 'default' model id", async () => {
|
||||
expect(snapshot.config.modeId).toBe("auto");
|
||||
});
|
||||
|
||||
test("createAgent injects daemon append system prompt at runtime only", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
const storage = new AgentStorage(storagePath, logger);
|
||||
const client = new TestAgentClient();
|
||||
const manager = new AgentManager({
|
||||
clients: {
|
||||
codex: client,
|
||||
},
|
||||
registry: storage,
|
||||
logger,
|
||||
appendSystemPrompt: " Daemon instructions. ",
|
||||
idFactory: () => "00000000-0000-4000-8000-000000000103",
|
||||
});
|
||||
|
||||
const snapshot = await manager.createAgent({
|
||||
provider: "codex",
|
||||
cwd: workdir,
|
||||
systemPrompt: "Agent instructions.",
|
||||
});
|
||||
const record = await storage.get(snapshot.id);
|
||||
|
||||
expect(client.createdConfigs[0]?.systemPrompt).toBe("Agent instructions.");
|
||||
expect(client.createdConfigs[0]?.daemonAppendSystemPrompt).toBe("Daemon instructions.");
|
||||
expect(snapshot.config.daemonAppendSystemPrompt).toBe("Daemon instructions.");
|
||||
expect(record?.config?.systemPrompt).toBe("Agent instructions.");
|
||||
expect(record?.config).not.toHaveProperty("daemonAppendSystemPrompt");
|
||||
});
|
||||
|
||||
test("daemon append system prompt is not injected into Pi", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
const storage = new AgentStorage(storagePath, logger);
|
||||
const client = new TestAgentClient();
|
||||
const manager = new AgentManager({
|
||||
clients: {
|
||||
pi: client as unknown as AgentClient,
|
||||
},
|
||||
providerDefinitions: {
|
||||
pi: { enabled: true },
|
||||
},
|
||||
registry: storage,
|
||||
logger,
|
||||
appendSystemPrompt: "Daemon instructions.",
|
||||
idFactory: () => "00000000-0000-4000-8000-000000000104",
|
||||
});
|
||||
|
||||
await manager.createAgent({
|
||||
provider: "pi",
|
||||
cwd: workdir,
|
||||
systemPrompt: "Agent instructions.",
|
||||
});
|
||||
|
||||
expect(client.createdConfigs[0]?.daemonAppendSystemPrompt).toBeUndefined();
|
||||
});
|
||||
|
||||
test("setAgentMode persists the selected mode across session reload", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
|
||||
@@ -173,6 +173,7 @@ export interface AgentManagerOptions {
|
||||
durableTimelineStore?: AgentTimelineStore;
|
||||
terminalManager?: TerminalManager | null;
|
||||
mcpBaseUrl?: string;
|
||||
appendSystemPrompt?: string;
|
||||
agentStreamCoalesceWindowMs?: number;
|
||||
rescueTimeouts?: AgentManagerRescueTimeouts;
|
||||
logger: Logger;
|
||||
@@ -438,6 +439,7 @@ export class AgentManager {
|
||||
private readonly backgroundTasks = new Set<Promise<void>>();
|
||||
private readonly agentStreamCoalescer: AgentStreamCoalescer;
|
||||
private mcpBaseUrl: string | null;
|
||||
private appendSystemPrompt: string;
|
||||
private onAgentAttention?: AgentAttentionCallback;
|
||||
private logger: Logger;
|
||||
private readonly rescueTimeouts: Required<AgentManagerRescueTimeouts>;
|
||||
@@ -448,6 +450,7 @@ export class AgentManager {
|
||||
this.durableTimelineStore = options?.durableTimelineStore;
|
||||
this.onAgentAttention = options?.onAgentAttention;
|
||||
this.mcpBaseUrl = options?.mcpBaseUrl ?? null;
|
||||
this.appendSystemPrompt = options.appendSystemPrompt ?? "";
|
||||
this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
|
||||
this.rescueTimeouts = {
|
||||
reloadSessionCloseMs:
|
||||
@@ -502,6 +505,10 @@ export class AgentManager {
|
||||
this.mcpBaseUrl = url;
|
||||
}
|
||||
|
||||
setAppendSystemPrompt(prompt: string | null | undefined): void {
|
||||
this.appendSystemPrompt = prompt ?? "";
|
||||
}
|
||||
|
||||
public getMetricsSnapshot(): AgentMetricsSnapshot {
|
||||
const byLifecycle: Record<string, number> = {};
|
||||
let withActiveForegroundTurn = 0;
|
||||
@@ -816,7 +823,9 @@ export class AgentManager {
|
||||
},
|
||||
};
|
||||
this.requireEnabledProvider(injectedConfig.provider);
|
||||
const normalizedConfig = await this.normalizeConfig(injectedConfig);
|
||||
const normalizedConfig = this.applyDaemonAppendSystemPrompt(
|
||||
await this.normalizeConfig(injectedConfig),
|
||||
);
|
||||
const launchContext = this.buildLaunchContext(resolvedAgentId);
|
||||
const client = await this.requireAvailableClient({
|
||||
provider: normalizedConfig.provider,
|
||||
@@ -860,7 +869,9 @@ export class AgentManager {
|
||||
...overrides,
|
||||
provider: handle.provider,
|
||||
} as AgentSessionConfig;
|
||||
const normalizedConfig = await this.normalizeConfig(mergedConfig);
|
||||
const normalizedConfig = this.applyDaemonAppendSystemPrompt(
|
||||
await this.normalizeConfig(mergedConfig),
|
||||
);
|
||||
const resumeOverrides: Partial<AgentSessionConfig> = { ...overrides };
|
||||
let hasResumeOverrides = overrides !== undefined;
|
||||
|
||||
@@ -874,6 +885,11 @@ export class AgentManager {
|
||||
hasResumeOverrides = true;
|
||||
}
|
||||
|
||||
if (metadata.daemonAppendSystemPrompt !== normalizedConfig.daemonAppendSystemPrompt) {
|
||||
resumeOverrides.daemonAppendSystemPrompt = normalizedConfig.daemonAppendSystemPrompt;
|
||||
hasResumeOverrides = true;
|
||||
}
|
||||
|
||||
const launchContext = this.buildLaunchContext(resolvedAgentId);
|
||||
const client = this.requireClient(handle.provider);
|
||||
const available = await client.isAvailable();
|
||||
@@ -919,7 +935,9 @@ export class AgentManager {
|
||||
...overrides,
|
||||
provider,
|
||||
} as AgentSessionConfig;
|
||||
const normalizedConfig = await this.normalizeConfig(refreshConfig);
|
||||
const normalizedConfig = this.applyDaemonAppendSystemPrompt(
|
||||
await this.normalizeConfig(refreshConfig),
|
||||
);
|
||||
const launchContext = this.buildLaunchContext(agentId);
|
||||
|
||||
const session = handle
|
||||
@@ -3437,6 +3455,26 @@ export class AgentManager {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private applyDaemonAppendSystemPrompt(config: AgentSessionConfig): AgentSessionConfig {
|
||||
if (config.provider === "pi") {
|
||||
const next = { ...config };
|
||||
delete next.daemonAppendSystemPrompt;
|
||||
return next;
|
||||
}
|
||||
|
||||
const trimmed = this.appendSystemPrompt.trim();
|
||||
if (!trimmed) {
|
||||
const next = { ...config };
|
||||
delete next.daemonAppendSystemPrompt;
|
||||
return next;
|
||||
}
|
||||
|
||||
return {
|
||||
...config,
|
||||
daemonAppendSystemPrompt: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
private buildLaunchContext(agentId: string): AgentLaunchContext {
|
||||
return {
|
||||
agentId,
|
||||
|
||||
@@ -473,6 +473,11 @@ export interface AgentSessionConfig {
|
||||
* Mapped by each provider to its native instruction field.
|
||||
*/
|
||||
systemPrompt?: string;
|
||||
/**
|
||||
* Daemon-level instructions appended at runtime. This is deliberately not
|
||||
* persisted into agent config so daemon setting changes apply cleanly.
|
||||
*/
|
||||
daemonAppendSystemPrompt?: string;
|
||||
modeId?: string;
|
||||
model?: string;
|
||||
thinkingOptionId?: string;
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Returns orchestrator mode instructions to append to the system prompt.
|
||||
* These instructions are from CLAUDE.md and guide agents on how to work
|
||||
* effectively in this repository.
|
||||
*/
|
||||
export function getOrchestratorModeInstructions(): string {
|
||||
return `
|
||||
<orchestrator-mode>
|
||||
Activation:
|
||||
- Only activate if the user explicitly says "go into orchestrator mode" (or similar).
|
||||
- Otherwise, do work directly yourself; do not spawn agents.
|
||||
|
||||
Core rules:
|
||||
- In orchestrator mode, you accomplish tasks only by managing agents; do not perform the work yourself.
|
||||
- Always prefix agent titles (e.g., "🎭 Feature Implementation", "🎭 Design Discussion").
|
||||
- Set cwd to the repository root and choose the most permissive mode available.
|
||||
- If an agent control tool call fails, list agents before launching another; it may just be a wait timeout.
|
||||
|
||||
Context management:
|
||||
- Reuse an existing agent when the next step needs the same context (same files/module/folder or immediate follow-up like investigate → fix in the same area).
|
||||
- Start a new agent when switching to a different area/module, or when an agent has run long and its context feels stale.
|
||||
- Prefer sending follow-up prompts to an existing agent to avoid reloading context.
|
||||
- Use multiple agents when roles diverge (e.g., one for refactor, one for external validation), but default to reuse when context overlaps.
|
||||
|
||||
Conversation with agents:
|
||||
- Engage actively: ask pointed questions, probe risks, and request clarifications before accepting proposals.
|
||||
- Encourage agents to validate assumptions, consider edge cases, and describe how they will test/verify.
|
||||
|
||||
Agent selection guidance:
|
||||
- Codex: methodical and slower; great for deep debugging, tracing code paths, refactoring, complex features, and design discussions.
|
||||
- Claude: fast; strong at tool use, agentic control, and managing other agents; may jump to conclusions—ask it to verify.
|
||||
|
||||
Clarifying ambiguous requests:
|
||||
- Research first to understand the current state.
|
||||
- Ask clarifying questions about what the user wants.
|
||||
- Present options with trade-offs.
|
||||
- Get explicit confirmation; never assume.
|
||||
|
||||
Investigation vs Implementation:
|
||||
- Investigate only unless explicitly asked to implement.
|
||||
- Report findings clearly.
|
||||
- After investigation, ask for direction before implementing.
|
||||
|
||||
Tool usage discipline:
|
||||
- Do not ask users to run commands—run them yourself.
|
||||
- Do not repeat the user’s instructions verbatim—summarize them in your own words.
|
||||
- Be explicit about results—tell the user what happened after every command.
|
||||
</orchestrator-mode>
|
||||
`;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ import {
|
||||
import { findExecutable, isCommandAvailable } from "../../../../utils/executable.js";
|
||||
import { withTimeout } from "../../../../utils/promise-timeout.js";
|
||||
import { execCommand } from "../../../../utils/spawn.js";
|
||||
import { getOrchestratorModeInstructions } from "../../orchestrator-instructions.js";
|
||||
import { composeSystemPromptParts } from "../../system-prompt.js";
|
||||
|
||||
const fsPromises = promises;
|
||||
const CLAUDE_SETTING_SOURCES: NonNullable<ClaudeOptions["settingSources"]> = [
|
||||
@@ -2314,9 +2314,9 @@ class ClaudeAgentSession implements AgentSession {
|
||||
}
|
||||
|
||||
private buildAppendedSystemPrompt(): string {
|
||||
return [getOrchestratorModeInstructions(), this.config.systemPrompt?.trim()]
|
||||
.filter((entry): entry is string => typeof entry === "string" && entry.length > 0)
|
||||
.join("\n\n");
|
||||
return (
|
||||
composeSystemPromptParts(this.config.systemPrompt, this.config.daemonAppendSystemPrompt) ?? ""
|
||||
);
|
||||
}
|
||||
|
||||
private buildSdkEnv(extraClaudeOptions: Partial<ClaudeOptions> | undefined): NodeJS.ProcessEnv {
|
||||
@@ -2365,7 +2365,7 @@ class ClaudeAgentSession implements AgentSession {
|
||||
canUseTool: this.handlePermissionRequest,
|
||||
pathToClaudeCodeExecutable: claudeBinary,
|
||||
// Use Claude Code preset system prompt and load CLAUDE.md files
|
||||
// Append provider-agnostic system prompt and orchestrator instructions for agents.
|
||||
// Append provider-agnostic system prompts for agents.
|
||||
systemPrompt: {
|
||||
type: "preset",
|
||||
preset: "claude_code",
|
||||
|
||||
@@ -41,6 +41,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { z } from "zod";
|
||||
import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
|
||||
import { composeSystemPromptParts } from "../system-prompt.js";
|
||||
import { curateAgentActivity } from "../activity-curator.js";
|
||||
import {
|
||||
mapCodexRolloutToolCall,
|
||||
@@ -3026,12 +3027,11 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
const settings: Record<string, unknown> = {};
|
||||
if (match.model) settings.model = match.model;
|
||||
if (match.reasoning_effort) settings.reasoning_effort = match.reasoning_effort;
|
||||
const developerInstructions = [
|
||||
match.developer_instructions?.trim(),
|
||||
this.config.systemPrompt?.trim(),
|
||||
]
|
||||
.filter((entry): entry is string => typeof entry === "string" && entry.length > 0)
|
||||
.join("\n\n");
|
||||
const developerInstructions = composeSystemPromptParts(
|
||||
match.developer_instructions,
|
||||
this.config.systemPrompt,
|
||||
this.config.daemonAppendSystemPrompt,
|
||||
);
|
||||
if (developerInstructions) settings.developer_instructions = developerInstructions;
|
||||
if (this.config.model) settings.model = this.config.model;
|
||||
const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
|
||||
@@ -3161,8 +3161,12 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
return;
|
||||
}
|
||||
const params: Record<string, unknown> = { threadId: this.currentThreadId };
|
||||
if (this.config.systemPrompt?.trim()) {
|
||||
params.developerInstructions = this.config.systemPrompt.trim();
|
||||
const developerInstructions = composeSystemPromptParts(
|
||||
this.config.systemPrompt,
|
||||
this.config.daemonAppendSystemPrompt,
|
||||
);
|
||||
if (developerInstructions) {
|
||||
params.developerInstructions = developerInstructions;
|
||||
}
|
||||
const codexConfig = this.buildCodexInnerConfig();
|
||||
if (codexConfig) {
|
||||
@@ -3257,6 +3261,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
approvalPolicy: string;
|
||||
sandboxPolicyType: string;
|
||||
hasOutputSchema: boolean;
|
||||
hasDeveloperInstructions: boolean;
|
||||
hasCodexConfig: boolean;
|
||||
}> {
|
||||
const input = await this.buildUserInput(prompt);
|
||||
@@ -3299,8 +3304,12 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
if (options?.outputSchema) {
|
||||
params.outputSchema = normalizeCodexOutputSchema(options.outputSchema);
|
||||
}
|
||||
if (this.config.systemPrompt?.trim()) {
|
||||
params.developerInstructions = this.config.systemPrompt.trim();
|
||||
const developerInstructions = composeSystemPromptParts(
|
||||
this.config.systemPrompt,
|
||||
this.config.daemonAppendSystemPrompt,
|
||||
);
|
||||
if (developerInstructions) {
|
||||
params.developerInstructions = developerInstructions;
|
||||
}
|
||||
const codexConfig = this.buildCodexInnerConfig();
|
||||
if (codexConfig) {
|
||||
@@ -3313,6 +3322,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
approvalPolicy,
|
||||
sandboxPolicyType,
|
||||
hasOutputSchema: Boolean(options?.outputSchema),
|
||||
hasDeveloperInstructions: Boolean(developerInstructions),
|
||||
hasCodexConfig: Boolean(codexConfig),
|
||||
};
|
||||
}
|
||||
@@ -3323,6 +3333,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
approvalPolicy,
|
||||
sandboxPolicyType,
|
||||
hasOutputSchema,
|
||||
hasDeveloperInstructions,
|
||||
hasCodexConfig,
|
||||
}: {
|
||||
turnId: string;
|
||||
@@ -3330,6 +3341,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
approvalPolicy: string;
|
||||
sandboxPolicyType: string;
|
||||
hasOutputSchema: boolean;
|
||||
hasDeveloperInstructions: boolean;
|
||||
hasCodexConfig: boolean;
|
||||
}): void {
|
||||
this.logger.info(
|
||||
@@ -3345,7 +3357,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
sandboxPolicyType,
|
||||
hasCollaborationMode: Boolean(this.resolvedCollaborationMode),
|
||||
hasOutputSchema,
|
||||
hasDeveloperInstructions: Boolean(this.config.systemPrompt?.trim()),
|
||||
hasDeveloperInstructions,
|
||||
hasCodexConfig,
|
||||
},
|
||||
"Starting Codex app-server turn",
|
||||
@@ -3407,6 +3419,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
approvalPolicy: turnStart.approvalPolicy,
|
||||
sandboxPolicyType: turnStart.sandboxPolicyType,
|
||||
hasOutputSchema: turnStart.hasOutputSchema,
|
||||
hasDeveloperInstructions: turnStart.hasDeveloperInstructions,
|
||||
hasCodexConfig: turnStart.hasCodexConfig,
|
||||
});
|
||||
await this.client.request("turn/start", turnStart.params, TURN_START_TIMEOUT_MS);
|
||||
@@ -3952,14 +3965,16 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
const approvalPolicy = this.config.approvalPolicy ?? preset.approvalPolicy;
|
||||
const sandbox = this.config.sandboxMode ?? preset.sandbox;
|
||||
const innerConfig = this.buildCodexInnerConfig();
|
||||
const developerInstructions = composeSystemPromptParts(
|
||||
this.config.systemPrompt,
|
||||
this.config.daemonAppendSystemPrompt,
|
||||
);
|
||||
const params: Record<string, unknown> = {
|
||||
model,
|
||||
cwd: this.config.cwd ?? null,
|
||||
approvalPolicy,
|
||||
sandbox,
|
||||
...(this.config.systemPrompt?.trim()
|
||||
? { developerInstructions: this.config.systemPrompt.trim() }
|
||||
: {}),
|
||||
...(developerInstructions ? { developerInstructions } : {}),
|
||||
...(innerConfig ? { config: innerConfig } : {}),
|
||||
...(this.ephemeral ? { ephemeral: true } : {}),
|
||||
};
|
||||
|
||||
@@ -60,6 +60,7 @@ import {
|
||||
} from "./diagnostic-utils.js";
|
||||
import { runProviderTurn } from "./provider-runner.js";
|
||||
import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
|
||||
import { composeSystemPromptParts } from "../system-prompt.js";
|
||||
import {
|
||||
createSdkOpenCodeClient,
|
||||
type OpenCodeRuntime,
|
||||
@@ -2589,6 +2590,10 @@ class OpenCodeAgentSession implements AgentSession {
|
||||
partTypes: parts.map((p) => p.type),
|
||||
});
|
||||
try {
|
||||
const systemPrompt = composeSystemPromptParts(
|
||||
this.config.systemPrompt,
|
||||
this.config.daemonAppendSystemPrompt,
|
||||
);
|
||||
const promptResponse = await this.client.session.promptAsync({
|
||||
sessionID: this.sessionId,
|
||||
directory: this.config.cwd,
|
||||
@@ -2601,7 +2606,7 @@ class OpenCodeAgentSession implements AgentSession {
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(this.config.systemPrompt ? { system: this.config.systemPrompt } : {}),
|
||||
...(systemPrompt ? { system: systemPrompt } : {}),
|
||||
...(model ? { model } : {}),
|
||||
...(effectiveMode ? { agent: effectiveMode } : {}),
|
||||
...(effectiveVariant ? { variant: effectiveVariant } : {}),
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export function composeSystemPromptParts(
|
||||
...parts: Array<string | null | undefined>
|
||||
): string | undefined {
|
||||
const prompt = parts
|
||||
.map((part) => part?.trim())
|
||||
.filter((part): part is string => typeof part === "string" && part.length > 0)
|
||||
.join("\n\n");
|
||||
|
||||
return prompt.length > 0 ? prompt : undefined;
|
||||
}
|
||||
@@ -230,6 +230,7 @@ export interface PaseoDaemonConfig {
|
||||
mcpEnabled?: boolean;
|
||||
mcpInjectIntoAgents?: boolean;
|
||||
autoArchiveAfterMerge?: boolean;
|
||||
appendSystemPrompt?: string;
|
||||
staticDir: string;
|
||||
mcpDebug: boolean;
|
||||
isDev?: boolean;
|
||||
@@ -290,6 +291,7 @@ export async function createPaseoDaemon(
|
||||
]),
|
||||
),
|
||||
autoArchiveAfterMerge: config.autoArchiveAfterMerge ?? false,
|
||||
appendSystemPrompt: config.appendSystemPrompt ?? "",
|
||||
},
|
||||
logger,
|
||||
);
|
||||
@@ -516,6 +518,7 @@ export async function createPaseoDaemon(
|
||||
},
|
||||
providerDefinitions: providerRegistry,
|
||||
registry: agentStorage,
|
||||
appendSystemPrompt: config.appendSystemPrompt,
|
||||
logger,
|
||||
});
|
||||
|
||||
@@ -862,6 +865,9 @@ export async function createPaseoDaemon(
|
||||
daemonConfigStore.onFieldChange("mcp.injectIntoAgents", (value) => {
|
||||
agentManager.setMcpBaseUrl(value ? mcpBaseUrl : null);
|
||||
});
|
||||
daemonConfigStore.onFieldChange("appendSystemPrompt", (value) => {
|
||||
agentManager.setAppendSystemPrompt(typeof value === "string" ? value : "");
|
||||
});
|
||||
const relayEnabled = config.relayEnabled ?? true;
|
||||
const relayEndpoint = config.relayEndpoint ?? "relay.paseo.sh:443";
|
||||
const relayPublicEndpoint = config.relayPublicEndpoint ?? relayEndpoint;
|
||||
|
||||
@@ -256,6 +256,10 @@ function resolveAuthConfig(
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function resolveAppendSystemPrompt(persisted: ReturnType<typeof loadPersistedConfig>): string {
|
||||
return persisted.daemon?.appendSystemPrompt ?? "";
|
||||
}
|
||||
|
||||
function resolveStaticLoadConfigSettings(
|
||||
env: NodeJS.ProcessEnv,
|
||||
cli: CliConfigOverrides | undefined,
|
||||
@@ -266,6 +270,7 @@ function resolveStaticLoadConfigSettings(
|
||||
mcpInjectIntoAgents:
|
||||
cli?.mcpInjectIntoAgents ?? persisted.daemon?.mcp?.injectIntoAgents ?? false,
|
||||
autoArchiveAfterMerge: persisted.daemon?.autoArchiveAfterMerge ?? false,
|
||||
appendSystemPrompt: resolveAppendSystemPrompt(persisted),
|
||||
hostnames: mergeHostnames([
|
||||
persisted.daemon?.hostnames,
|
||||
parseHostnamesEnv(env.PASEO_HOSTNAMES ?? env.PASEO_ALLOWED_HOSTS),
|
||||
@@ -286,8 +291,14 @@ export function loadConfig(
|
||||
const persisted = loadPersistedConfig(paseoHome);
|
||||
|
||||
const listen = resolveListenAddress(env, options?.cli, persisted);
|
||||
const { mcpEnabled, mcpInjectIntoAgents, autoArchiveAfterMerge, hostnames, appBaseUrl } =
|
||||
resolveStaticLoadConfigSettings(env, options?.cli, persisted);
|
||||
const {
|
||||
mcpEnabled,
|
||||
mcpInjectIntoAgents,
|
||||
autoArchiveAfterMerge,
|
||||
appendSystemPrompt,
|
||||
hostnames,
|
||||
appBaseUrl,
|
||||
} = resolveStaticLoadConfigSettings(env, options?.cli, persisted);
|
||||
|
||||
const relay = resolveRelayConfig({
|
||||
env,
|
||||
@@ -315,6 +326,7 @@ export function loadConfig(
|
||||
mcpEnabled,
|
||||
mcpInjectIntoAgents,
|
||||
autoArchiveAfterMerge,
|
||||
appendSystemPrompt,
|
||||
mcpDebug: env.MCP_DEBUG === "1",
|
||||
isDev: resolvePaseoNodeEnv(env) === "development",
|
||||
agentStoragePath: path.join(paseoHome, "agents"),
|
||||
|
||||
@@ -107,6 +107,28 @@ describe("DaemonConfigStore", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("patch persists append system prompt into config.json", () => {
|
||||
const paseoHome = mkdtempSync(path.join(tmpdir(), "paseo-daemon-config-store-"));
|
||||
tempDirs.push(paseoHome);
|
||||
|
||||
const store = new DaemonConfigStore(
|
||||
paseoHome,
|
||||
{
|
||||
mcp: { injectIntoAgents: false },
|
||||
providers: {},
|
||||
appendSystemPrompt: "",
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
|
||||
store.patch({
|
||||
appendSystemPrompt: "Prefer terse replies.",
|
||||
});
|
||||
|
||||
const persisted = loadPersistedConfig(paseoHome);
|
||||
expect(persisted.daemon?.appendSystemPrompt).toBe("Prefer terse replies.");
|
||||
});
|
||||
|
||||
test("patch persists provider additional models into config.json", () => {
|
||||
const paseoHome = mkdtempSync(path.join(tmpdir(), "paseo-daemon-config-store-"));
|
||||
tempDirs.push(paseoHome);
|
||||
|
||||
@@ -186,6 +186,7 @@ function mergeMutableConfigIntoPersistedConfig(params: {
|
||||
injectIntoAgents: mutable.mcp.injectIntoAgents,
|
||||
},
|
||||
autoArchiveAfterMerge: mutable.autoArchiveAfterMerge,
|
||||
appendSystemPrompt: mutable.appendSystemPrompt,
|
||||
},
|
||||
agents:
|
||||
providerOverrides && Object.keys(providerOverrides).length > 0
|
||||
|
||||
@@ -34,6 +34,18 @@ describe("PersistedConfigSchema daemon auth config", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("PersistedConfigSchema daemon append system prompt config", () => {
|
||||
test("accepts optional append system prompt", () => {
|
||||
const parsed = PersistedConfigSchema.parse({
|
||||
daemon: {
|
||||
appendSystemPrompt: "Prefer terse replies.",
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.daemon?.appendSystemPrompt).toBe("Prefer terse replies.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("PersistedConfigSchema daemon relay config", () => {
|
||||
test("accepts optional relay TLS setting", () => {
|
||||
const parsed = PersistedConfigSchema.parse({
|
||||
|
||||
@@ -251,6 +251,7 @@ export const PersistedConfigSchema = z
|
||||
.passthrough()
|
||||
.optional(),
|
||||
autoArchiveAfterMerge: z.boolean().optional(),
|
||||
appendSystemPrompt: z.string().optional(),
|
||||
cors: z
|
||||
.object({
|
||||
allowedOrigins: z.array(z.string()).optional(),
|
||||
|
||||
@@ -114,6 +114,7 @@ export const MutableDaemonConfigSchema = z
|
||||
.passthrough(),
|
||||
providers: z.record(z.string(), MutableDaemonProviderConfigSchema).default({}),
|
||||
autoArchiveAfterMerge: z.boolean().default(false),
|
||||
appendSystemPrompt: z.string().default(""),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
@@ -124,6 +125,7 @@ export const MutableDaemonConfigPatchSchema = z
|
||||
.record(z.string(), MutableDaemonProviderConfigSchema.partial().passthrough())
|
||||
.optional(),
|
||||
autoArchiveAfterMerge: z.boolean().optional(),
|
||||
appendSystemPrompt: z.string().optional(),
|
||||
})
|
||||
.partial()
|
||||
.passthrough();
|
||||
|
||||
Reference in New Issue
Block a user