@happycastle/oh-my-openclaw 0.12.5 → 0.13.1

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.
Files changed (47) hide show
  1. package/dist/__tests__/helpers/mock-factory.d.ts +26 -0
  2. package/dist/__tests__/helpers/mock-factory.js +66 -0
  3. package/dist/agents/agent-configs.d.ts +0 -4
  4. package/dist/agents/agent-configs.js +11 -6
  5. package/dist/agents/agent-ids.d.ts +14 -0
  6. package/dist/agents/agent-ids.js +54 -0
  7. package/dist/agents/persona-prompts.js +7 -21
  8. package/dist/cli/model-presets.d.ts +2 -1
  9. package/dist/cli/model-presets.js +2 -13
  10. package/dist/cli/setup.d.ts +1 -0
  11. package/dist/cli/setup.js +37 -2
  12. package/dist/cli.js +4 -2
  13. package/dist/commands/persona-commands.js +11 -10
  14. package/dist/commands/status-commands.js +5 -2
  15. package/dist/commands/workflow-commands.js +40 -40
  16. package/dist/constants.d.ts +5 -0
  17. package/dist/constants.js +7 -0
  18. package/dist/features/context-collector.d.ts +1 -0
  19. package/dist/features/context-collector.js +46 -16
  20. package/dist/hooks/context-injector.js +3 -2
  21. package/dist/hooks/message-monitor.js +11 -2
  22. package/dist/hooks/persona-injector.js +9 -7
  23. package/dist/hooks/todo-enforcer.d.ts +1 -1
  24. package/dist/hooks/todo-enforcer.js +8 -14
  25. package/dist/index.js +31 -75
  26. package/dist/services/ralph-loop.js +49 -29
  27. package/dist/tools/checkpoint.js +83 -101
  28. package/dist/tools/look-at.js +68 -53
  29. package/dist/tools/task-delegation.js +25 -40
  30. package/dist/types.d.ts +2 -24
  31. package/dist/types.js +2 -4
  32. package/dist/utils/config.js +1 -1
  33. package/dist/utils/helpers.d.ts +26 -0
  34. package/dist/utils/helpers.js +51 -0
  35. package/dist/utils/paths.d.ts +3 -0
  36. package/dist/utils/paths.js +13 -0
  37. package/dist/utils/persona-state.d.ts +6 -5
  38. package/dist/utils/persona-state.js +31 -23
  39. package/dist/utils/state.d.ts +1 -1
  40. package/dist/utils/state.js +6 -2
  41. package/openclaw.plugin.json +5 -5
  42. package/package.json +1 -1
  43. package/skills/delegation-prompt.md +61 -48
  44. package/skills/workflow-tool-patterns.md +2 -2
  45. package/workflows/plan.md +1 -1
  46. package/workflows/start-work.md +7 -8
  47. package/workflows/ultrawork.md +4 -3
@@ -0,0 +1,26 @@
1
+ import type { OmocPluginApi, PluginConfig, TypedHookContext } from '../../types.js';
2
+ /**
3
+ * Creates a fully-typed mock OmocPluginApi with sensible defaults.
4
+ * Superset of all mock shapes used across 7 test files.
5
+ * All methods are vi.fn() mocks for assertion support.
6
+ *
7
+ * @param overrides - Partial config overrides (spread last)
8
+ * @returns Mock OmocPluginApi with all required methods
9
+ */
10
+ export declare function createMockApi(overrides?: Partial<OmocPluginApi>): OmocPluginApi;
11
+ /**
12
+ * Creates a fully-typed mock PluginConfig with sensible defaults.
13
+ * Includes all fields from PluginConfig interface.
14
+ *
15
+ * @param overrides - Partial config overrides (spread last)
16
+ * @returns Mock PluginConfig with all required fields
17
+ */
18
+ export declare function createMockConfig(overrides?: Partial<PluginConfig>): PluginConfig;
19
+ /**
20
+ * Creates a fully-typed mock TypedHookContext.
21
+ * Provides context for hook handlers (agentId, sessionKey, sessionId, etc).
22
+ *
23
+ * @param overrides - Partial context overrides (spread last)
24
+ * @returns Mock TypedHookContext with sensible defaults
25
+ */
26
+ export declare function createMockContext(overrides?: Partial<TypedHookContext>): TypedHookContext;
@@ -0,0 +1,66 @@
1
+ import { vi } from 'vitest';
2
+ /**
3
+ * Creates a fully-typed mock OmocPluginApi with sensible defaults.
4
+ * Superset of all mock shapes used across 7 test files.
5
+ * All methods are vi.fn() mocks for assertion support.
6
+ *
7
+ * @param overrides - Partial config overrides (spread last)
8
+ * @returns Mock OmocPluginApi with all required methods
9
+ */
10
+ export function createMockApi(overrides) {
11
+ return {
12
+ config: createMockConfig(),
13
+ logger: {
14
+ info: vi.fn(),
15
+ warn: vi.fn(),
16
+ error: vi.fn(),
17
+ },
18
+ registerHook: vi.fn(),
19
+ registerTool: vi.fn(),
20
+ registerCommand: vi.fn(),
21
+ registerService: vi.fn(),
22
+ registerGatewayMethod: vi.fn(),
23
+ registerCli: vi.fn(),
24
+ on: vi.fn(),
25
+ ...overrides,
26
+ };
27
+ }
28
+ /**
29
+ * Creates a fully-typed mock PluginConfig with sensible defaults.
30
+ * Includes all fields from PluginConfig interface.
31
+ *
32
+ * @param overrides - Partial config overrides (spread last)
33
+ * @returns Mock PluginConfig with all required fields
34
+ */
35
+ export function createMockConfig(overrides) {
36
+ return {
37
+ max_ralph_iterations: 10,
38
+ todo_enforcer_enabled: false,
39
+ todo_enforcer_cooldown_ms: 2000,
40
+ todo_enforcer_max_failures: 5,
41
+ comment_checker_enabled: true,
42
+ notepad_dir: 'workspace/notepads',
43
+ plans_dir: 'workspace/plans',
44
+ checkpoint_dir: 'workspace/checkpoints',
45
+ tmux_socket: '/tmp/openclaw-tmux-sockets/openclaw.sock',
46
+ model_routing: undefined,
47
+ ...overrides,
48
+ };
49
+ }
50
+ /**
51
+ * Creates a fully-typed mock TypedHookContext.
52
+ * Provides context for hook handlers (agentId, sessionKey, sessionId, etc).
53
+ *
54
+ * @param overrides - Partial context overrides (spread last)
55
+ * @returns Mock TypedHookContext with sensible defaults
56
+ */
57
+ export function createMockContext(overrides) {
58
+ return {
59
+ agentId: 'test-agent',
60
+ sessionKey: 'test-session-key',
61
+ sessionId: 'test-session-id',
62
+ workspaceDir: '/tmp/test-workspace',
63
+ messageProvider: undefined,
64
+ ...overrides,
65
+ };
66
+ }
@@ -1,7 +1,3 @@
1
- /**
2
- * Defines the Oh-My-OpenClaw plugin's local agent configuration contracts
3
- * and the canonical list of built-in OMOC agent definitions.
4
- */
5
1
  export type OmocAgentConfig = {
6
2
  id: string;
7
3
  name?: string;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Defines the Oh-My-OpenClaw plugin's local agent configuration contracts
3
+ * and the canonical list of built-in OMOC agent definitions.
4
+ */
5
+ import { READ_ONLY_DENY } from '../constants.js';
1
6
  export const OMOC_AGENT_CONFIGS = [
2
7
  // Strategic planning agent.
3
8
  {
@@ -82,7 +87,7 @@ export const OMOC_AGENT_CONFIGS = [
82
87
  },
83
88
  tools: {
84
89
  profile: 'coding',
85
- deny: ['write', 'edit', 'apply_patch', 'sessions_spawn'],
90
+ deny: READ_ONLY_DENY,
86
91
  },
87
92
  },
88
93
  // Read-only codebase search specialist.
@@ -97,7 +102,7 @@ export const OMOC_AGENT_CONFIGS = [
97
102
  },
98
103
  tools: {
99
104
  profile: 'coding',
100
- deny: ['write', 'edit', 'apply_patch', 'sessions_spawn'],
105
+ deny: READ_ONLY_DENY,
101
106
  },
102
107
  },
103
108
  // Read-only documentation research specialist.
@@ -112,7 +117,7 @@ export const OMOC_AGENT_CONFIGS = [
112
117
  },
113
118
  tools: {
114
119
  profile: 'coding',
115
- deny: ['write', 'edit', 'apply_patch', 'sessions_spawn'],
120
+ deny: READ_ONLY_DENY,
116
121
  },
117
122
  },
118
123
  // Read-only pre-planning analyst.
@@ -130,7 +135,7 @@ export const OMOC_AGENT_CONFIGS = [
130
135
  },
131
136
  tools: {
132
137
  profile: 'coding',
133
- deny: ['write', 'edit', 'apply_patch', 'sessions_spawn'],
138
+ deny: READ_ONLY_DENY,
134
139
  },
135
140
  },
136
141
  // Read-only plan review specialist.
@@ -148,7 +153,7 @@ export const OMOC_AGENT_CONFIGS = [
148
153
  },
149
154
  tools: {
150
155
  profile: 'coding',
151
- deny: ['write', 'edit', 'apply_patch', 'sessions_spawn'],
156
+ deny: READ_ONLY_DENY,
152
157
  },
153
158
  },
154
159
  // Multimodal visual analysis specialist (read-only — allowlist approach).
@@ -166,7 +171,7 @@ export const OMOC_AGENT_CONFIGS = [
166
171
  },
167
172
  tools: {
168
173
  allow: ['read'],
169
- deny: ['write', 'edit', 'apply_patch', 'sessions_spawn'],
174
+ deny: READ_ONLY_DENY,
170
175
  },
171
176
  },
172
177
  // Frontend-focused visual engineering specialist.
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Canonical source for all agent IDs and their metadata.
3
+ * Single source of truth for agent identification across the plugin.
4
+ */
5
+ /** Orchestrator-tier agents (strategic planning and task distribution) */
6
+ export declare const ORCHESTRATOR_IDS: Set<string>;
7
+ /** Worker-tier agents (implementation and execution) */
8
+ export declare const WORKER_IDS: Set<string>;
9
+ /** Maps agent ID to markdown persona filename (without extension) */
10
+ export declare const AGENT_MD_MAP: Record<string, string>;
11
+ /** Maps agent ID to model tier for provider preset selection */
12
+ export declare const AGENT_TIER_MAP: Record<string, 'planning' | 'worker' | 'orchestrator' | 'lightweight' | 'visual'>;
13
+ /** All agent IDs (orchestrators + workers + read-only specialists) */
14
+ export declare const ALL_AGENT_IDS: string[];
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Canonical source for all agent IDs and their metadata.
3
+ * Single source of truth for agent identification across the plugin.
4
+ */
5
+ /** Orchestrator-tier agents (strategic planning and task distribution) */
6
+ export const ORCHESTRATOR_IDS = new Set([
7
+ 'omoc_prometheus',
8
+ 'omoc_atlas',
9
+ ]);
10
+ /** Worker-tier agents (implementation and execution) */
11
+ export const WORKER_IDS = new Set([
12
+ 'omoc_sisyphus',
13
+ 'omoc_hephaestus',
14
+ 'omoc_frontend',
15
+ ]);
16
+ /** Maps agent ID to markdown persona filename (without extension) */
17
+ export const AGENT_MD_MAP = {
18
+ omoc_atlas: 'atlas',
19
+ omoc_prometheus: 'prometheus',
20
+ omoc_sisyphus: 'sisyphus-junior',
21
+ omoc_hephaestus: 'hephaestus',
22
+ omoc_oracle: 'oracle',
23
+ omoc_explore: 'explore',
24
+ omoc_librarian: 'librarian',
25
+ omoc_metis: 'metis',
26
+ omoc_momus: 'momus',
27
+ omoc_looker: 'multimodal-looker',
28
+ omoc_frontend: 'frontend',
29
+ };
30
+ /** Maps agent ID to model tier for provider preset selection */
31
+ export const AGENT_TIER_MAP = {
32
+ omoc_prometheus: 'planning',
33
+ omoc_oracle: 'planning',
34
+ omoc_metis: 'planning',
35
+ omoc_momus: 'planning',
36
+ omoc_sisyphus: 'worker',
37
+ omoc_hephaestus: 'worker',
38
+ omoc_atlas: 'orchestrator',
39
+ omoc_explore: 'lightweight',
40
+ omoc_librarian: 'lightweight',
41
+ omoc_looker: 'visual',
42
+ omoc_frontend: 'visual',
43
+ };
44
+ /** All agent IDs (orchestrators + workers + read-only specialists) */
45
+ export const ALL_AGENT_IDS = [
46
+ ...ORCHESTRATOR_IDS,
47
+ ...WORKER_IDS,
48
+ 'omoc_oracle',
49
+ 'omoc_explore',
50
+ 'omoc_librarian',
51
+ 'omoc_metis',
52
+ 'omoc_momus',
53
+ 'omoc_looker',
54
+ ];
@@ -1,30 +1,14 @@
1
1
  import { readFileSync, statSync } from 'fs';
2
2
  import { promises as fs } from 'fs';
3
- import { dirname, join } from 'path';
4
- import { fileURLToPath } from 'url';
3
+ import { join } from 'path';
5
4
  import { OMOC_AGENT_CONFIGS } from './agent-configs.js';
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = dirname(__filename);
8
- // From dist/agents/ → plugin root is ../../ (same pattern as workflow-commands.ts)
9
- const PLUGIN_ROOT = join(__dirname, '..', '..');
5
+ import { PLUGIN_ROOT } from '../utils/paths.js';
6
+ import { AGENT_MD_MAP } from './agent-ids.js';
10
7
  const personaCache = new Map();
11
8
  /** Clear all cached persona file contents. Useful for testing. */
12
9
  export function clearPersonaCache() {
13
10
  personaCache.clear();
14
11
  }
15
- const AGENT_MD_MAP = {
16
- omoc_atlas: 'atlas',
17
- omoc_prometheus: 'prometheus',
18
- omoc_sisyphus: 'sisyphus-junior',
19
- omoc_hephaestus: 'hephaestus',
20
- omoc_oracle: 'oracle',
21
- omoc_explore: 'explore',
22
- omoc_librarian: 'librarian',
23
- omoc_metis: 'metis',
24
- omoc_momus: 'momus',
25
- omoc_looker: 'multimodal-looker',
26
- omoc_frontend: 'frontend',
27
- };
28
12
  const SHORT_ID_MAP = {};
29
13
  for (const id of Object.keys(AGENT_MD_MAP)) {
30
14
  SHORT_ID_MAP[id.replace('omoc_', '')] = id;
@@ -57,7 +41,8 @@ export function readPersonaPromptSync(agentId) {
57
41
  personaCache.set(agentPath, { content, mtimeMs: stat.mtimeMs });
58
42
  return content;
59
43
  }
60
- catch {
44
+ catch (error) {
45
+ console.warn('[omoc] Failed to read persona file synchronously:', agentPath, error);
61
46
  personaCache.delete(agentPath);
62
47
  return `[OmOC] Could not read persona file: agents/${mdName}.md (looked in ${agentPath})`;
63
48
  }
@@ -71,7 +56,8 @@ export async function readPersonaPrompt(agentId) {
71
56
  try {
72
57
  return await fs.readFile(agentPath, 'utf-8');
73
58
  }
74
- catch {
59
+ catch (error) {
60
+ console.warn('[omoc] Failed to read persona file asynchronously:', agentPath, error);
75
61
  return `[OmOC] Could not read persona file: agents/${mdName}.md (looked in ${agentPath})`;
76
62
  }
77
63
  }
@@ -1,3 +1,4 @@
1
+ import { AGENT_TIER_MAP } from '../agents/agent-ids.js';
1
2
  export type ModelTier = 'planning' | 'worker' | 'orchestrator' | 'lightweight' | 'visual';
2
3
  export type ModelConfig = {
3
4
  primary: string;
@@ -5,7 +6,7 @@ export type ModelConfig = {
5
6
  };
6
7
  export type ProviderPreset = Record<ModelTier, ModelConfig>;
7
8
  export declare const PROVIDER_PRESETS: Record<string, ProviderPreset>;
8
- export declare const AGENT_TIER_MAP: Record<string, ModelTier>;
9
+ export { AGENT_TIER_MAP };
9
10
  export declare const PROVIDER_LABELS: Record<string, string>;
10
11
  export declare const MODEL_TIERS: ModelTier[];
11
12
  export declare function buildCustomPreset(tierModels: Record<ModelTier, string>): ProviderPreset;
@@ -1,3 +1,4 @@
1
+ import { AGENT_TIER_MAP } from '../agents/agent-ids.js';
1
2
  export const PROVIDER_PRESETS = {
2
3
  anthropic: {
3
4
  planning: { primary: 'anthropic/claude-opus-4-6', fallbacks: ['openai/gpt-5.3-codex'] },
@@ -21,19 +22,7 @@ export const PROVIDER_PRESETS = {
21
22
  visual: { primary: 'google/gemini-2.5-pro', fallbacks: ['google/gemini-3-flash'] },
22
23
  },
23
24
  };
24
- export const AGENT_TIER_MAP = {
25
- omoc_prometheus: 'planning',
26
- omoc_oracle: 'planning',
27
- omoc_metis: 'planning',
28
- omoc_momus: 'planning',
29
- omoc_sisyphus: 'worker',
30
- omoc_hephaestus: 'worker',
31
- omoc_atlas: 'orchestrator',
32
- omoc_explore: 'lightweight',
33
- omoc_librarian: 'lightweight',
34
- omoc_looker: 'visual',
35
- omoc_frontend: 'visual',
36
- };
25
+ export { AGENT_TIER_MAP };
37
26
  export const PROVIDER_LABELS = {
38
27
  anthropic: 'Anthropic (Claude)',
39
28
  openai: 'OpenAI (GPT)',
@@ -19,6 +19,7 @@ export declare function findConfigPath(workspaceDir?: string): string | undefine
19
19
  /**
20
20
  * Parse OpenClaw config using JSON5 (matches OpenClaw's own parser).
21
21
  * Handles comments, trailing commas, unquoted keys, multi-line strings, etc.
22
+ * Validates the resulting shape before returning.
22
23
  */
23
24
  export declare function parseConfig(raw: string): ConfigShape;
24
25
  export declare function serializeConfig(config: ConfigShape): string;
package/dist/cli/setup.js CHANGED
@@ -34,15 +34,50 @@ export function findConfigPath(workspaceDir) {
34
34
  }
35
35
  return undefined;
36
36
  }
37
+ /**
38
+ * Validate that parsed config has the expected shape.
39
+ * Throws descriptive error if validation fails.
40
+ */
41
+ function validateConfigShape(data) {
42
+ if (!data || typeof data !== 'object') {
43
+ throw new Error('Invalid config: expected object at root level');
44
+ }
45
+ const config = data;
46
+ // If agents section exists, validate its structure
47
+ if (config.agents !== undefined) {
48
+ if (typeof config.agents !== 'object' || config.agents === null) {
49
+ throw new Error('Invalid config: agents must be an object');
50
+ }
51
+ const agents = config.agents;
52
+ if (agents.list !== undefined) {
53
+ if (!Array.isArray(agents.list)) {
54
+ throw new Error('Invalid config: agents.list must be an array');
55
+ }
56
+ // Validate each agent has an id
57
+ for (let i = 0; i < agents.list.length; i++) {
58
+ const agent = agents.list[i];
59
+ if (!agent || typeof agent !== 'object') {
60
+ throw new Error(`Invalid config: agents.list[${i}] must be an object`);
61
+ }
62
+ if (!('id' in agent) || typeof agent.id !== 'string') {
63
+ throw new Error(`Invalid config: agents.list[${i}] must have a string id field`);
64
+ }
65
+ }
66
+ }
67
+ }
68
+ return config;
69
+ }
37
70
  /**
38
71
  * Parse OpenClaw config using JSON5 (matches OpenClaw's own parser).
39
72
  * Handles comments, trailing commas, unquoted keys, multi-line strings, etc.
73
+ * Validates the resulting shape before returning.
40
74
  */
41
75
  export function parseConfig(raw) {
42
- return JSON5.parse(raw);
76
+ const parsed = JSON5.parse(raw);
77
+ return validateConfigShape(parsed);
43
78
  }
44
79
  export function serializeConfig(config) {
45
- return JSON.stringify(config, null, 2) + '\n';
80
+ return JSON5.stringify(config, null, 2) + '\n';
46
81
  }
47
82
  export function mergeAgentConfigs(existing, incoming, force) {
48
83
  const result = { added: [], skipped: [], updated: [] };
package/dist/cli.js CHANGED
@@ -51,7 +51,8 @@ function commandExists(cmd) {
51
51
  execSync(`command -v ${cmd}`, { stdio: 'ignore' });
52
52
  return true;
53
53
  }
54
- catch {
54
+ catch (error) {
55
+ // command not found — expected for optional tools
55
56
  return false;
56
57
  }
57
58
  }
@@ -77,7 +78,8 @@ function isSymlink(p) {
77
78
  const lstat = lstatSync(p);
78
79
  return lstat.isSymbolicLink();
79
80
  }
80
- catch {
81
+ catch (error) {
82
+ // path does not exist or is not accessible — expected for missing files
81
83
  return false;
82
84
  }
83
85
  }
@@ -1,4 +1,5 @@
1
- import { getActivePersona, setActivePersona, resetPersonaState } from '../utils/persona-state.js';
1
+ import { LOG_PREFIX } from '../constants.js';
2
+ import { getActivePersona, setActivePersonaId, resetPersonaState } from '../utils/persona-state.js';
2
3
  import { resolvePersonaId, listPersonas, DEFAULT_PERSONA_ID } from '../agents/persona-prompts.js';
3
4
  function getDisplayName(personaId) {
4
5
  const persona = listPersonas().find((p) => p.id === personaId);
@@ -10,12 +11,12 @@ export function registerPersonaCommands(api) {
10
11
  description: 'OmOC mode — activate, switch, or list personas',
11
12
  acceptsArgs: true,
12
13
  handler: async (ctx) => {
13
- api.logger.info(`[omoc] /omoc command received — raw ctx.args: ${JSON.stringify(ctx.args)}, ctx keys: ${JSON.stringify(Object.keys(ctx))}`);
14
+ api.logger.info(`${LOG_PREFIX} /omoc command received — raw ctx.args: ${JSON.stringify(ctx.args)}, ctx keys: ${JSON.stringify(Object.keys(ctx))}`);
14
15
  const args = (ctx.args ?? '').trim().toLowerCase();
15
- api.logger.info(`[omoc] Parsed args: "${args}" (length: ${args.length})`);
16
+ api.logger.info(`${LOG_PREFIX} Parsed args: "${args}" (length: ${args.length})`);
16
17
  if (!args) {
17
- const previousId = getActivePersona();
18
- setActivePersona(DEFAULT_PERSONA_ID);
18
+ const previousId = await getActivePersona();
19
+ await setActivePersonaId(DEFAULT_PERSONA_ID);
19
20
  const name = getDisplayName(DEFAULT_PERSONA_ID);
20
21
  const switchNote = previousId && previousId !== DEFAULT_PERSONA_ID
21
22
  ? `\n\nSwitched from **${getDisplayName(previousId)}**.`
@@ -25,9 +26,9 @@ export function registerPersonaCommands(api) {
25
26
  };
26
27
  }
27
28
  if (args === 'off') {
28
- const wasActive = getActivePersona();
29
+ const wasActive = await getActivePersona();
29
30
  const wasName = wasActive ? getDisplayName(wasActive) : null;
30
- resetPersonaState();
31
+ await resetPersonaState();
31
32
  return {
32
33
  text: wasName
33
34
  ? `# OmOC Mode: OFF\n\nPersona **${wasName}** deactivated. Applied immediately — your next message will use default behavior.`
@@ -36,7 +37,7 @@ export function registerPersonaCommands(api) {
36
37
  }
37
38
  if (args === 'list') {
38
39
  const personas = listPersonas();
39
- const activeId = getActivePersona();
40
+ const activeId = await getActivePersona();
40
41
  const lines = personas.map((p) => {
41
42
  const active = p.id === activeId ? ' ← active' : '';
42
43
  return `| ${p.emoji} | \`${p.shortName}\` | ${p.displayName} | ${p.theme} |${active}`;
@@ -63,8 +64,8 @@ export function registerPersonaCommands(api) {
63
64
  text: `# Unknown Persona: "${args}"\n\nAvailable personas: ${available}\n\nUse \`/omoc list\` for details.`,
64
65
  };
65
66
  }
66
- const previousId = getActivePersona();
67
- setActivePersona(resolvedId);
67
+ const previousId = await getActivePersona();
68
+ await setActivePersonaId(resolvedId);
68
69
  const displayName = getDisplayName(resolvedId);
69
70
  const switched = listPersonas().find((p) => p.id === resolvedId);
70
71
  const switchNote = previousId && previousId !== resolvedId
@@ -30,8 +30,11 @@ export function registerStatusCommands(api) {
30
30
  const config = getConfig(api);
31
31
  const safeConfig = {};
32
32
  for (const [key, value] of Object.entries(config)) {
33
- if (typeof value === 'string' && (key.includes('token') || key.includes('secret') || key.includes('key'))) {
34
- safeConfig[key] = '***';
33
+ if (typeof value === 'string') {
34
+ const lowerKey = key.toLowerCase();
35
+ const isSensitive = lowerKey.includes('token') || lowerKey.includes('key') ||
36
+ lowerKey.includes('secret') || lowerKey.includes('password');
37
+ safeConfig[key] = isSensitive ? '***' : value;
35
38
  }
36
39
  else {
37
40
  safeConfig[key] = value;
@@ -1,54 +1,54 @@
1
1
  import { promises as fs } from 'fs';
2
- import { dirname, join } from 'path';
3
- import { fileURLToPath } from 'url';
4
- const __filename = fileURLToPath(import.meta.url);
5
- const __dirname = dirname(__filename);
6
- // From dist/commands/ → plugin root is ../../
7
- const PLUGIN_ROOT = join(__dirname, '..', '..');
2
+ import { resolvePluginPath } from '../utils/paths.js';
8
3
  async function readWorkflow(workflowName) {
9
4
  try {
10
- const workflowPath = join(PLUGIN_ROOT, 'workflows', `${workflowName}.md`);
5
+ const workflowPath = resolvePluginPath('workflows', `${workflowName}.md`);
11
6
  return await fs.readFile(workflowPath, 'utf-8');
12
7
  }
13
- catch {
14
- return `Error: Could not read workflow file 'workflows/${workflowName}.md'. Plugin root: ${PLUGIN_ROOT}`;
8
+ catch (error) {
9
+ console.warn('[omoc] Failed to read workflow file:', `workflows/${workflowName}.md`, error);
10
+ return `Error: Could not read workflow file 'workflows/${workflowName}.md'.`;
15
11
  }
16
12
  }
17
- export function registerWorkflowCommands(api) {
18
- api.registerCommand({
13
+ const WORKFLOW_COMMANDS = [
14
+ {
19
15
  name: 'ultrawork',
20
16
  description: 'Full planning → execution → verification workflow',
21
- acceptsArgs: true,
22
- handler: async (ctx) => {
23
- const taskDescription = ctx.args || 'No task specified';
24
- const workflow = await readWorkflow('ultrawork');
25
- return {
26
- text: `# Ultrawork Mode\n\n**Task**: ${taskDescription}\n\n---\n\n${workflow}`,
27
- };
28
- },
29
- });
30
- api.registerCommand({
17
+ workflowFile: 'ultrawork',
18
+ headerTitle: 'Ultrawork Mode',
19
+ argLabel: 'Task',
20
+ defaultArgValue: 'No task specified',
21
+ },
22
+ {
31
23
  name: 'plan',
32
24
  description: 'Create a structured execution plan',
33
- acceptsArgs: true,
34
- handler: async (ctx) => {
35
- const topic = ctx.args || 'No topic specified';
36
- const workflow = await readWorkflow('plan');
37
- return {
38
- text: `# Planning Mode\n\n**Topic**: ${topic}\n\n---\n\n${workflow}`,
39
- };
40
- },
41
- });
42
- api.registerCommand({
25
+ workflowFile: 'plan',
26
+ headerTitle: 'Planning Mode',
27
+ argLabel: 'Topic',
28
+ defaultArgValue: 'No topic specified',
29
+ },
30
+ {
43
31
  name: 'start_work',
44
32
  description: 'Execute an approved plan',
45
- acceptsArgs: true,
46
- handler: async (ctx) => {
47
- const planPath = ctx.args || 'most recent plan';
48
- const workflow = await readWorkflow('start-work');
49
- return {
50
- text: `# Start Work Mode\n\n**Plan**: ${planPath}\n\n---\n\n${workflow}`,
51
- };
52
- },
53
- });
33
+ workflowFile: 'start-work',
34
+ headerTitle: 'Start Work Mode',
35
+ argLabel: 'Plan',
36
+ defaultArgValue: 'most recent plan',
37
+ },
38
+ ];
39
+ export function registerWorkflowCommands(api) {
40
+ for (const config of WORKFLOW_COMMANDS) {
41
+ api.registerCommand({
42
+ name: config.name,
43
+ description: config.description,
44
+ acceptsArgs: true,
45
+ handler: async (ctx) => {
46
+ const argValue = ctx.args || config.defaultArgValue;
47
+ const workflow = await readWorkflow(config.workflowFile);
48
+ return {
49
+ text: `# ${config.headerTitle}\n\n**${config.argLabel}**: ${argValue}\n\n---\n\n${workflow}`,
50
+ };
51
+ },
52
+ });
53
+ }
54
54
  }
@@ -1,2 +1,7 @@
1
+ export declare const PLUGIN_ID = "oh-my-openclaw";
2
+ export declare const TOOL_PREFIX = "omoc_";
3
+ export declare const ABSOLUTE_MAX_RALPH_ITERATIONS = 100;
4
+ export declare const LOG_PREFIX = "[omoc]";
5
+ export declare const READ_ONLY_DENY: string[];
1
6
  export declare const CATEGORIES: readonly ["quick", "deep", "ultrabrain", "visual-engineering", "multimodal", "artistry", "unspecified-low", "unspecified-high", "writing"];
2
7
  export type Category = (typeof CATEGORIES)[number];
package/dist/constants.js CHANGED
@@ -1,3 +1,10 @@
1
+ // Plugin constants
2
+ export const PLUGIN_ID = 'oh-my-openclaw';
3
+ export const TOOL_PREFIX = 'omoc_';
4
+ export const ABSOLUTE_MAX_RALPH_ITERATIONS = 100;
5
+ export const LOG_PREFIX = '[omoc]';
6
+ export const READ_ONLY_DENY = ['write', 'edit', 'apply_patch', 'sessions_spawn'];
7
+ // Category definitions
1
8
  export const CATEGORIES = [
2
9
  'quick',
3
10
  'deep',
@@ -25,6 +25,7 @@ export declare class ContextCollector {
25
25
  getEntries(sessionKey: string): ContextEntry[];
26
26
  hasEntries(sessionKey: string): boolean;
27
27
  private getOrCreateSession;
28
+ private pruneExpiredSessions;
28
29
  private sortEntries;
29
30
  }
30
31
  export declare const contextCollector: ContextCollector;