Merge pull request #161 from phj1081/codex/owner/ejclaw
revert: remove prompt bootstrap injection
This commit is contained in:
@@ -1,127 +0,0 @@
|
|||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
||||||
|
|
||||||
import {
|
|
||||||
preparePromptIngestion,
|
|
||||||
recordPromptIngestion,
|
|
||||||
} from './agent-prompt-ingestion.js';
|
|
||||||
|
|
||||||
describe('agent prompt ingestion', () => {
|
|
||||||
let tempDir: string;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tempDir = fs.mkdtempSync('/tmp/ejclaw-prompt-ingestion-');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
function writeClaudePrompt(text: string): string {
|
|
||||||
const claudeDir = path.join(tempDir, '.claude');
|
|
||||||
fs.mkdirSync(claudeDir, { recursive: true });
|
|
||||||
const promptPath = path.join(claudeDir, 'CLAUDE.md');
|
|
||||||
fs.writeFileSync(promptPath, text);
|
|
||||||
return claudeDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeCodexPrompt(text: string): string {
|
|
||||||
const codexDir = path.join(tempDir, '.codex');
|
|
||||||
fs.mkdirSync(codexDir, { recursive: true });
|
|
||||||
const promptPath = path.join(codexDir, 'AGENTS.md');
|
|
||||||
fs.writeFileSync(promptPath, text);
|
|
||||||
return codexDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
it('injects the role prompt when starting a fresh Claude session', () => {
|
|
||||||
const claudeDir = writeClaudePrompt('platform rules\n\npaired rules\n');
|
|
||||||
|
|
||||||
const plan = preparePromptIngestion({
|
|
||||||
agentType: 'claude-code',
|
|
||||||
env: { CLAUDE_CONFIG_DIR: claudeDir },
|
|
||||||
prompt: 'review current changes',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(plan.prompt).toContain('<ejclaw_role_prompt>');
|
|
||||||
expect(plan.prompt).toContain('platform rules');
|
|
||||||
expect(plan.prompt).toContain('paired rules');
|
|
||||||
expect(plan.prompt).toContain('Current task:\nreview current changes');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not reinject an unchanged prompt into an existing session', () => {
|
|
||||||
const codexDir = writeCodexPrompt('codex platform rules\n');
|
|
||||||
const first = preparePromptIngestion({
|
|
||||||
agentType: 'codex',
|
|
||||||
env: { CODEX_HOME: codexDir },
|
|
||||||
prompt: 'first task',
|
|
||||||
});
|
|
||||||
recordPromptIngestion(first);
|
|
||||||
|
|
||||||
const second = preparePromptIngestion({
|
|
||||||
agentType: 'codex',
|
|
||||||
env: { CODEX_HOME: codexDir },
|
|
||||||
prompt: 'second task',
|
|
||||||
sessionId: 'session-existing',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(second.prompt).toBe('second task');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('reinjects when the prompt pack changes in an existing session', () => {
|
|
||||||
const claudeDir = writeClaudePrompt('old rules\n');
|
|
||||||
const first = preparePromptIngestion({
|
|
||||||
agentType: 'claude-code',
|
|
||||||
env: { CLAUDE_CONFIG_DIR: claudeDir },
|
|
||||||
prompt: 'first task',
|
|
||||||
sessionId: 'session-existing',
|
|
||||||
});
|
|
||||||
recordPromptIngestion(first);
|
|
||||||
|
|
||||||
fs.writeFileSync(path.join(claudeDir, 'CLAUDE.md'), 'new rules\n');
|
|
||||||
const second = preparePromptIngestion({
|
|
||||||
agentType: 'claude-code',
|
|
||||||
env: { CLAUDE_CONFIG_DIR: claudeDir },
|
|
||||||
prompt: 'second task',
|
|
||||||
sessionId: 'session-existing',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(second.prompt).toContain('new rules');
|
|
||||||
expect(second.prompt).toContain('Current task:\nsecond task');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not treat a one-time memory briefing as a prompt pack change', () => {
|
|
||||||
const claudeDir = writeClaudePrompt(
|
|
||||||
'stable rules\n\n---\n\n## Shared Room Memory\n- remembered',
|
|
||||||
);
|
|
||||||
const first = preparePromptIngestion({
|
|
||||||
agentType: 'claude-code',
|
|
||||||
env: { CLAUDE_CONFIG_DIR: claudeDir },
|
|
||||||
prompt: 'first task',
|
|
||||||
memoryBriefing: '## Shared Room Memory\n- remembered',
|
|
||||||
});
|
|
||||||
recordPromptIngestion(first);
|
|
||||||
|
|
||||||
fs.writeFileSync(path.join(claudeDir, 'CLAUDE.md'), 'stable rules\n');
|
|
||||||
const second = preparePromptIngestion({
|
|
||||||
agentType: 'claude-code',
|
|
||||||
env: { CLAUDE_CONFIG_DIR: claudeDir },
|
|
||||||
prompt: 'second task',
|
|
||||||
sessionId: 'session-existing',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(second.prompt).toBe('second task');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not inject role prompts into compact commands', () => {
|
|
||||||
const claudeDir = writeClaudePrompt('platform rules\n');
|
|
||||||
|
|
||||||
const plan = preparePromptIngestion({
|
|
||||||
agentType: 'claude-code',
|
|
||||||
env: { CLAUDE_CONFIG_DIR: claudeDir },
|
|
||||||
prompt: '/compact',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(plan.prompt).toBe('/compact');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
import crypto from 'crypto';
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
import type { AgentType } from './types.js';
|
|
||||||
|
|
||||||
const PROMPT_INGESTION_STATE_FILE = '.ejclaw-prompt-ingestion.json';
|
|
||||||
|
|
||||||
export interface PromptIngestionPlan {
|
|
||||||
prompt: string;
|
|
||||||
fingerprint?: string;
|
|
||||||
statePath?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hashPrompt(prompt: string): string {
|
|
||||||
return crypto.createHash('sha256').update(prompt).digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
function readStoredFingerprint(statePath: string): string | undefined {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(fs.readFileSync(statePath, 'utf-8')) as {
|
|
||||||
fingerprint?: unknown;
|
|
||||||
};
|
|
||||||
return typeof parsed.fingerprint === 'string'
|
|
||||||
? parsed.fingerprint
|
|
||||||
: undefined;
|
|
||||||
} catch {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripTrailingMemoryBriefing(
|
|
||||||
promptContext: string,
|
|
||||||
memoryBriefing?: string,
|
|
||||||
): string {
|
|
||||||
if (!memoryBriefing) {
|
|
||||||
return promptContext;
|
|
||||||
}
|
|
||||||
const suffix = `\n\n---\n\n${memoryBriefing}`;
|
|
||||||
return promptContext.endsWith(suffix)
|
|
||||||
? promptContext.slice(0, -suffix.length)
|
|
||||||
: promptContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolvePromptContextPath(args: {
|
|
||||||
agentType: AgentType;
|
|
||||||
env: Record<string, string>;
|
|
||||||
}): string | undefined {
|
|
||||||
if (args.agentType === 'codex' && args.env.CODEX_HOME) {
|
|
||||||
return path.join(args.env.CODEX_HOME, 'AGENTS.md');
|
|
||||||
}
|
|
||||||
if (args.env.CLAUDE_CONFIG_DIR) {
|
|
||||||
return path.join(args.env.CLAUDE_CONFIG_DIR, 'CLAUDE.md');
|
|
||||||
}
|
|
||||||
if (args.env.CODEX_HOME) {
|
|
||||||
return path.join(args.env.CODEX_HOME, 'AGENTS.md');
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPromptBootstrap(promptContext: string, prompt: string): string {
|
|
||||||
return `System bootstrap:
|
|
||||||
Load and follow the current EJClaw role prompt below. This bootstrap is injected only when a role session starts or when the prompt pack changes. Do not summarize it; apply it silently to the task that follows.
|
|
||||||
|
|
||||||
<ejclaw_role_prompt>
|
|
||||||
${promptContext}
|
|
||||||
</ejclaw_role_prompt>
|
|
||||||
|
|
||||||
Current task:
|
|
||||||
${prompt}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function preparePromptIngestion(args: {
|
|
||||||
agentType: AgentType;
|
|
||||||
env: Record<string, string>;
|
|
||||||
prompt: string;
|
|
||||||
sessionId?: string;
|
|
||||||
memoryBriefing?: string;
|
|
||||||
}): PromptIngestionPlan {
|
|
||||||
if (args.prompt.trim() === '/compact') {
|
|
||||||
return { prompt: args.prompt };
|
|
||||||
}
|
|
||||||
|
|
||||||
const promptContextPath = resolvePromptContextPath(args);
|
|
||||||
if (!promptContextPath || !fs.existsSync(promptContextPath)) {
|
|
||||||
return { prompt: args.prompt };
|
|
||||||
}
|
|
||||||
|
|
||||||
const promptContext = fs.readFileSync(promptContextPath, 'utf-8').trim();
|
|
||||||
if (!promptContext) {
|
|
||||||
return { prompt: args.prompt };
|
|
||||||
}
|
|
||||||
|
|
||||||
const fingerprintSource = stripTrailingMemoryBriefing(
|
|
||||||
promptContext,
|
|
||||||
args.memoryBriefing,
|
|
||||||
).trim();
|
|
||||||
const fingerprint = hashPrompt(fingerprintSource || promptContext);
|
|
||||||
const statePath = path.join(
|
|
||||||
path.dirname(promptContextPath),
|
|
||||||
PROMPT_INGESTION_STATE_FILE,
|
|
||||||
);
|
|
||||||
const storedFingerprint = readStoredFingerprint(statePath);
|
|
||||||
|
|
||||||
if (args.sessionId && storedFingerprint === fingerprint) {
|
|
||||||
return { prompt: args.prompt, fingerprint, statePath };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
prompt: buildPromptBootstrap(promptContext, args.prompt),
|
|
||||||
fingerprint,
|
|
||||||
statePath,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function recordPromptIngestion(plan: PromptIngestionPlan): void {
|
|
||||||
if (!plan.fingerprint || !plan.statePath) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fs.mkdirSync(path.dirname(plan.statePath), { recursive: true });
|
|
||||||
fs.writeFileSync(
|
|
||||||
plan.statePath,
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
fingerprint: plan.fingerprint,
|
|
||||||
ingested_at: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
) + '\n',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -12,10 +12,6 @@ import {
|
|||||||
prepareReadonlySessionEnvironment,
|
prepareReadonlySessionEnvironment,
|
||||||
prepareGroupEnvironment,
|
prepareGroupEnvironment,
|
||||||
} from './agent-runner-environment.js';
|
} from './agent-runner-environment.js';
|
||||||
import {
|
|
||||||
preparePromptIngestion,
|
|
||||||
recordPromptIngestion,
|
|
||||||
} from './agent-prompt-ingestion.js';
|
|
||||||
import { runSpawnedAgentProcess } from './agent-runner-process.js';
|
import { runSpawnedAgentProcess } from './agent-runner-process.js';
|
||||||
import { getStoredRoomSkillOverrides } from './db.js';
|
import { getStoredRoomSkillOverrides } from './db.js';
|
||||||
export {
|
export {
|
||||||
@@ -168,19 +164,6 @@ export async function runAgentProcess(
|
|||||||
'Spawning agent process',
|
'Spawning agent process',
|
||||||
);
|
);
|
||||||
|
|
||||||
const runnerInput: AgentInput = {
|
|
||||||
...input,
|
|
||||||
...(group.agentConfig?.codexGoals === true ? { codexGoals: true } : {}),
|
|
||||||
};
|
|
||||||
const promptIngestion = preparePromptIngestion({
|
|
||||||
agentType: group.agentType || 'claude-code',
|
|
||||||
env,
|
|
||||||
prompt: runnerInput.prompt,
|
|
||||||
sessionId: runnerInput.sessionId,
|
|
||||||
memoryBriefing: runnerInput.memoryBriefing,
|
|
||||||
});
|
|
||||||
runnerInput.prompt = promptIngestion.prompt;
|
|
||||||
|
|
||||||
const logsDir = path.join(groupDir, 'logs');
|
const logsDir = path.join(groupDir, 'logs');
|
||||||
fs.mkdirSync(logsDir, { recursive: true });
|
fs.mkdirSync(logsDir, { recursive: true });
|
||||||
|
|
||||||
@@ -193,6 +176,10 @@ export async function runAgentProcess(
|
|||||||
|
|
||||||
onProcess(proc, processName, env.EJCLAW_IPC_DIR);
|
onProcess(proc, processName, env.EJCLAW_IPC_DIR);
|
||||||
|
|
||||||
|
const runnerInput: AgentInput = {
|
||||||
|
...input,
|
||||||
|
...(group.agentConfig?.codexGoals === true ? { codexGoals: true } : {}),
|
||||||
|
};
|
||||||
proc.stdin.write(JSON.stringify(runnerInput));
|
proc.stdin.write(JSON.stringify(runnerInput));
|
||||||
proc.stdin.end();
|
proc.stdin.end();
|
||||||
|
|
||||||
@@ -204,11 +191,6 @@ export async function runAgentProcess(
|
|||||||
logsDir,
|
logsDir,
|
||||||
startTime,
|
startTime,
|
||||||
onOutput,
|
onOutput,
|
||||||
}).then((result) => {
|
}).then(resolve);
|
||||||
if (result.status === 'success') {
|
|
||||||
recordPromptIngestion(promptIngestion);
|
|
||||||
}
|
|
||||||
resolve(result);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user