@happycastle/oh-my-openclaw 0.13.3 → 0.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,8 +27,6 @@ export interface MergeResult {
27
27
  added: string[];
28
28
  skipped: string[];
29
29
  updated: string[];
30
- mcporterAdded?: string[];
31
- mcporterSkipped?: string[];
32
30
  }
33
31
  export declare function mergeAgentConfigs(existing: Array<{
34
32
  id: string;
@@ -43,7 +41,6 @@ export declare function mergeAgentConfigs(existing: Array<{
43
41
  export declare function applyProviderToConfigs(configs: OmocAgentConfig[], provider: string): OmocAgentConfig[];
44
42
  export declare function runInteractiveSetup(logger: Logger): Promise<{
45
43
  provider: string;
46
- setupMcporter: boolean;
47
44
  }>;
48
45
  export interface SetupOptions {
49
46
  configPath?: string;
@@ -51,8 +48,6 @@ export interface SetupOptions {
51
48
  force?: boolean;
52
49
  dryRun?: boolean;
53
50
  provider?: string;
54
- setupMcporter?: boolean;
55
- mcporterConfigPath?: string;
56
51
  interactive?: boolean;
57
52
  logger: Logger;
58
53
  }
package/dist/cli/setup.js CHANGED
@@ -4,7 +4,6 @@ import * as readline from 'node:readline';
4
4
  import JSON5 from 'json5';
5
5
  import { OMOC_AGENT_CONFIGS } from '../agents/agent-configs.js';
6
6
  import { PROVIDER_PRESETS, PROVIDER_LABELS, AGENT_TIER_MAP, MODEL_TIERS, applyProviderPreset, getProviderNames, buildCustomPreset, registerCustomPreset, } from './model-presets.js';
7
- import { OMOC_MCP_SERVERS, runMcporterSetup } from './mcporter-setup.js';
8
7
  const CONFIG_FILENAMES = [
9
8
  'openclaw.json5',
10
9
  'openclaw.json',
@@ -149,7 +148,8 @@ function printPreview(logger, provider) {
149
148
  }
150
149
  async function runCustomProviderFlow(rl, logger) {
151
150
  logger.info('');
152
- logger.info('Step 1/3: Select your AI provider');
151
+ logger.info(' Enter model IDs for each tier.');
152
+ logger.info(' Format: provider/model (e.g., cliproxy/claude-opus-4-6, z.ai/gpt-5.3-codex)');
153
153
  logger.info('');
154
154
  const tierModels = {};
155
155
  for (const tier of MODEL_TIERS) {
@@ -211,29 +211,17 @@ export async function runInteractiveSetup(logger) {
211
211
  logger.info('');
212
212
  logger.info(` ✓ Selected: ${PROVIDER_LABELS[provider] ?? 'Custom'}`);
213
213
  logger.info('');
214
- logger.info('Step 2/3: Model configuration preview');
214
+ logger.info('Step 2/2: Model configuration preview');
215
215
  logger.info('');
216
216
  printPreview(logger, provider);
217
217
  logger.info('');
218
218
  const confirm = await askQuestion(rl, ' Apply this configuration? (Y/n): ');
219
219
  if (confirm.toLowerCase() === 'n' || confirm.toLowerCase() === 'no') {
220
220
  logger.info(' Setup cancelled.');
221
- return { provider: '', setupMcporter: false };
221
+ return { provider: '' };
222
222
  }
223
223
  logger.info('');
224
- logger.info('Step 3/3: Web search MCP servers');
225
- logger.info('');
226
- logger.info(' OmOC agents use mcporter MCP servers for web search,');
227
- logger.info(' documentation lookup, and code search:');
228
- logger.info('');
229
- for (const [name, entry] of Object.entries(OMOC_MCP_SERVERS)) {
230
- logger.info(` ${name}: ${entry.description}`);
231
- }
232
- logger.info('');
233
- const mcpConfirm = await askQuestion(rl, ' Set up these MCP servers? (Y/n): ');
234
- const setupMcporter = mcpConfirm.toLowerCase() !== 'n' && mcpConfirm.toLowerCase() !== 'no';
235
- logger.info('');
236
- return { provider, setupMcporter };
224
+ return { provider };
237
225
  }
238
226
  finally {
239
227
  rl.close();
@@ -293,17 +281,6 @@ export function runSetup(options) {
293
281
  if (result.added.length === 0 && result.updated.length === 0) {
294
282
  logger.info('No changes needed — all OmOC agents already present.');
295
283
  }
296
- if (options.setupMcporter) {
297
- logger.info('');
298
- logger.info('Setting up mcporter MCP servers...');
299
- const mcpResult = runMcporterSetup({
300
- configPath: options.mcporterConfigPath,
301
- dryRun,
302
- logger,
303
- });
304
- result.mcporterAdded = mcpResult.added;
305
- result.mcporterSkipped = mcpResult.skipped;
306
- }
307
284
  return result;
308
285
  }
309
286
  export function registerSetupCli(ctx) {
@@ -322,13 +299,11 @@ export function registerSetupCli(ctx) {
322
299
  const valid = getProviderNames().join(', ');
323
300
  throw new Error(`Unknown provider "${provider}". Valid: ${valid}`);
324
301
  }
325
- let setupMcporter = false;
326
302
  if (!provider && process.stdin.isTTY) {
327
303
  const result = await runInteractiveSetup(ctx.logger);
328
304
  if (!result.provider)
329
305
  return;
330
306
  provider = result.provider;
331
- setupMcporter = result.setupMcporter;
332
307
  }
333
308
  runSetup({
334
309
  configPath: opts.config,
@@ -336,7 +311,6 @@ export function registerSetupCli(ctx) {
336
311
  force: provider ? true : opts.force,
337
312
  dryRun: opts.dryRun,
338
313
  provider,
339
- setupMcporter,
340
314
  logger: ctx.logger,
341
315
  });
342
316
  ctx.logger.info('');
@@ -1,2 +1,10 @@
1
1
  import { OmocPluginApi } from '../types.js';
2
+ /**
3
+ * Check whether persona content is already present in session history.
4
+ *
5
+ * OpenClaw's `prependContext` is merged into the user prompt and persisted
6
+ * in session history. Without this check the persona text accumulates —
7
+ * appearing once per historical user message sent to the model.
8
+ */
9
+ export declare function isPersonaAlreadyInHistory(messages: unknown[] | undefined, personaContent: string): boolean;
2
10
  export declare function registerPersonaInjector(api: OmocPluginApi): void;
@@ -24,6 +24,33 @@ async function resolveEffectivePersona(ctx) {
24
24
  return null;
25
25
  return { personaId: resolved, source: 'auto' };
26
26
  }
27
+ const FINGERPRINT_LENGTH = 200;
28
+ /**
29
+ * Check whether persona content is already present in session history.
30
+ *
31
+ * OpenClaw's `prependContext` is merged into the user prompt and persisted
32
+ * in session history. Without this check the persona text accumulates —
33
+ * appearing once per historical user message sent to the model.
34
+ */
35
+ export function isPersonaAlreadyInHistory(messages, personaContent) {
36
+ if (!messages || messages.length === 0)
37
+ return false;
38
+ const fingerprint = personaContent.slice(0, FINGERPRINT_LENGTH).trim();
39
+ if (!fingerprint)
40
+ return false;
41
+ for (const msg of messages) {
42
+ if (!msg || typeof msg !== 'object')
43
+ continue;
44
+ const record = msg;
45
+ if (record['role'] !== 'user')
46
+ continue;
47
+ const content = record['content'];
48
+ if (typeof content === 'string' && content.includes(fingerprint)) {
49
+ return true;
50
+ }
51
+ }
52
+ return false;
53
+ }
27
54
  export function registerPersonaInjector(api) {
28
55
  // Use the typed hook system (api.on) for before_prompt_build.
29
56
  // This directly injects into the system prompt via prependContext,
@@ -32,7 +59,7 @@ export function registerPersonaInjector(api) {
32
59
  // api.registerHook('before_prompt_build', ...) registers into the internal
33
60
  // hook system which does NOT trigger before_prompt_build — only hookRunner
34
61
  // (typed hooks via api.on) does.
35
- api.on('before_prompt_build', async (_event, ctx) => {
62
+ api.on('before_prompt_build', async (event, ctx) => {
36
63
  const result = await resolveEffectivePersona(ctx);
37
64
  if (!result) {
38
65
  const manual = await getActivePersona();
@@ -42,6 +69,10 @@ export function registerPersonaInjector(api) {
42
69
  const { personaId, source } = result;
43
70
  try {
44
71
  const content = readPersonaPromptSync(personaId);
72
+ if (isPersonaAlreadyInHistory(event.messages, content)) {
73
+ api.logger.info(`${LOG_PREFIX} Persona already in history, skipping injection: ${personaId} (${source})`);
74
+ return;
75
+ }
45
76
  api.logger.info(`${LOG_PREFIX} Persona injected via before_prompt_build: ${personaId} (${source}, agentId=${ctx.agentId ?? 'none'})`);
46
77
  return {
47
78
  prependContext: content,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happycastle/oh-my-openclaw",
3
- "version": "0.13.3",
3
+ "version": "0.13.4",
4
4
  "description": "Oh-My-OpenClaw plugin — multi-agent orchestration, todo enforcer, ralph loop, and custom tools for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,41 +0,0 @@
1
- type McpServerEntry = {
2
- url: string;
3
- description: string;
4
- };
5
- export declare const OMOC_MCP_SERVERS: Record<string, McpServerEntry>;
6
- type McporterConfig = {
7
- mcpServers: Record<string, {
8
- url?: string;
9
- baseUrl?: string;
10
- type?: string;
11
- [key: string]: unknown;
12
- }>;
13
- [key: string]: unknown;
14
- };
15
- /**
16
- * Resolve mcporter config path.
17
- * Priority: ~/.openclaw/workspace/config/mcporter.json > ~/.config/mcporter/mcporter.json
18
- */
19
- export declare function resolveMcporterConfigPath(): string;
20
- export declare function readMcporterConfig(configPath: string): McporterConfig;
21
- export declare function writeMcporterConfig(configPath: string, config: McporterConfig): void;
22
- export interface McporterMergeResult {
23
- added: string[];
24
- skipped: string[];
25
- }
26
- export declare function mergeMcpServers(existing: McporterConfig, servers: Record<string, McpServerEntry>): {
27
- config: McporterConfig;
28
- result: McporterMergeResult;
29
- };
30
- type Logger = {
31
- info: (msg: string) => void;
32
- warn: (msg: string) => void;
33
- error: (msg: string) => void;
34
- };
35
- export interface McporterSetupOptions {
36
- configPath?: string;
37
- dryRun?: boolean;
38
- logger: Logger;
39
- }
40
- export declare function runMcporterSetup(options: McporterSetupOptions): McporterMergeResult;
41
- export {};
@@ -1,102 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- export const OMOC_MCP_SERVERS = {
4
- 'web-search-prime': {
5
- url: 'https://api.z.ai/api/mcp/web_search_prime/mcp',
6
- description: 'Keyword-based web search (news, blogs, general)',
7
- },
8
- 'web-reader': {
9
- url: 'https://api.z.ai/api/mcp/web_reader/mcp',
10
- description: 'Clean full-page content extraction',
11
- },
12
- exa: {
13
- url: 'https://mcp.exa.ai/mcp?tools=web_search_exa',
14
- description: 'Semantic web search (Exa)',
15
- },
16
- context7: {
17
- url: 'https://mcp.context7.com/mcp',
18
- description: 'Library/framework documentation search',
19
- },
20
- grep_app: {
21
- url: 'https://mcp.grep.app',
22
- description: 'Open-source code search on GitHub',
23
- },
24
- zread: {
25
- url: 'https://api.z.ai/api/mcp/zread/mcp',
26
- description: 'Direct GitHub repository exploration',
27
- },
28
- };
29
- /**
30
- * Resolve mcporter config path.
31
- * Priority: ~/.openclaw/workspace/config/mcporter.json > ~/.config/mcporter/mcporter.json
32
- */
33
- export function resolveMcporterConfigPath() {
34
- const homeDir = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '';
35
- const openclawPath = path.join(homeDir, '.openclaw', 'workspace', 'config', 'mcporter.json');
36
- if (fs.existsSync(openclawPath)) {
37
- return openclawPath;
38
- }
39
- const mcporterHomePath = path.join(homeDir, '.config', 'mcporter', 'mcporter.json');
40
- if (fs.existsSync(mcporterHomePath)) {
41
- return mcporterHomePath;
42
- }
43
- return openclawPath;
44
- }
45
- export function readMcporterConfig(configPath) {
46
- if (!fs.existsSync(configPath)) {
47
- return { mcpServers: {} };
48
- }
49
- const raw = fs.readFileSync(configPath, 'utf-8');
50
- const parsed = JSON.parse(raw);
51
- if (!parsed.mcpServers || typeof parsed.mcpServers !== 'object') {
52
- parsed.mcpServers = {};
53
- }
54
- return parsed;
55
- }
56
- export function writeMcporterConfig(configPath, config) {
57
- const dir = path.dirname(configPath);
58
- if (!fs.existsSync(dir)) {
59
- fs.mkdirSync(dir, { recursive: true });
60
- }
61
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
62
- }
63
- export function mergeMcpServers(existing, servers) {
64
- const result = { added: [], skipped: [] };
65
- const merged = { ...existing, mcpServers: { ...existing.mcpServers } };
66
- for (const [name, entry] of Object.entries(servers)) {
67
- if (merged.mcpServers[name]) {
68
- result.skipped.push(name);
69
- }
70
- else {
71
- merged.mcpServers[name] = { url: entry.url };
72
- result.added.push(name);
73
- }
74
- }
75
- return { config: merged, result };
76
- }
77
- export function runMcporterSetup(options) {
78
- const { logger, dryRun = false } = options;
79
- const configPath = options.configPath ?? resolveMcporterConfigPath();
80
- logger.info(`mcporter config: ${configPath}`);
81
- const existing = readMcporterConfig(configPath);
82
- const { config: merged, result } = mergeMcpServers(existing, OMOC_MCP_SERVERS);
83
- if (result.added.length === 0) {
84
- logger.info('No changes needed — all MCP servers already configured.');
85
- return result;
86
- }
87
- if (dryRun) {
88
- logger.info(`[dry-run] Would add ${result.added.length} MCP server(s): ${result.added.join(', ')}`);
89
- return result;
90
- }
91
- if (fs.existsSync(configPath)) {
92
- const backupPath = configPath + '.bak';
93
- fs.copyFileSync(configPath, backupPath);
94
- logger.info(`Backup created: ${backupPath}`);
95
- }
96
- writeMcporterConfig(configPath, merged);
97
- logger.info(`Added ${result.added.length} MCP server(s): ${result.added.join(', ')}`);
98
- if (result.skipped.length > 0) {
99
- logger.info(`Skipped ${result.skipped.length} existing server(s): ${result.skipped.join(', ')}`);
100
- }
101
- return result;
102
- }