zigrix 0.1.0-alpha.8 → 0.1.0

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 (86) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +159 -120
  3. package/dist/agents/registry.js +19 -2
  4. package/dist/agents/roles.d.ts +10 -0
  5. package/dist/agents/roles.js +83 -0
  6. package/dist/config/defaults.d.ts +88 -6
  7. package/dist/config/defaults.js +82 -50
  8. package/dist/config/load.d.ts +5 -3
  9. package/dist/config/load.js +69 -30
  10. package/dist/config/schema.d.ts +46 -4
  11. package/dist/config/schema.js +49 -3
  12. package/dist/configure.d.ts +2 -0
  13. package/dist/configure.js +37 -14
  14. package/dist/dashboard/.next/BUILD_ID +1 -1
  15. package/dist/dashboard/.next/app-build-manifest.json +13 -13
  16. package/dist/dashboard/.next/app-path-routes-manifest.json +3 -3
  17. package/dist/dashboard/.next/build-manifest.json +2 -2
  18. package/dist/dashboard/.next/prerender-manifest.json +6 -6
  19. package/dist/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/dist/dashboard/.next/server/app/_not-found.html +1 -1
  21. package/dist/dashboard/.next/server/app/_not-found.rsc +1 -1
  22. package/dist/dashboard/.next/server/app/api/auth/login/route.js +1 -1
  23. package/dist/dashboard/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  24. package/dist/dashboard/.next/server/app/api/auth/logout/route.js +1 -1
  25. package/dist/dashboard/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  26. package/dist/dashboard/.next/server/app/api/auth/session/route.js +1 -1
  27. package/dist/dashboard/.next/server/app/api/auth/session/route_client-reference-manifest.js +1 -1
  28. package/dist/dashboard/.next/server/app/api/auth/setup/route.js +1 -1
  29. package/dist/dashboard/.next/server/app/api/auth/setup/route_client-reference-manifest.js +1 -1
  30. package/dist/dashboard/.next/server/app/api/overview/route_client-reference-manifest.js +1 -1
  31. package/dist/dashboard/.next/server/app/api/stream/route_client-reference-manifest.js +1 -1
  32. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  33. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/conversation/route_client-reference-manifest.js +1 -1
  34. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  35. package/dist/dashboard/.next/server/app/login/page_client-reference-manifest.js +1 -1
  36. package/dist/dashboard/.next/server/app/login.html +1 -1
  37. package/dist/dashboard/.next/server/app/login.rsc +1 -1
  38. package/dist/dashboard/.next/server/app/page.js +2 -2
  39. package/dist/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/dist/dashboard/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  41. package/dist/dashboard/.next/server/app/setup.html +1 -1
  42. package/dist/dashboard/.next/server/app/setup.rsc +1 -1
  43. package/dist/dashboard/.next/server/app-paths-manifest.json +3 -3
  44. package/dist/dashboard/.next/server/chunks/972.js +1 -1
  45. package/dist/dashboard/.next/server/functions-config-manifest.json +3 -3
  46. package/dist/dashboard/.next/server/middleware.js +1 -1
  47. package/dist/dashboard/.next/server/pages/404.html +1 -1
  48. package/dist/dashboard/.next/server/pages/500.html +1 -1
  49. package/dist/dashboard/.next/static/chunks/app/page-0314989c31e18b4b.js +1 -0
  50. package/dist/dashboard/.next/static/css/{94d75aff24d0c077.css → c3a7306cb2ba3f6c.css} +1 -1
  51. package/dist/dashboard.js +47 -0
  52. package/dist/doctor.js +28 -5
  53. package/dist/index.js +175 -171
  54. package/dist/onboard.d.ts +76 -2
  55. package/dist/onboard.js +529 -25
  56. package/dist/orchestration/dispatch.d.ts +3 -1
  57. package/dist/orchestration/dispatch.js +173 -45
  58. package/dist/orchestration/evidence.js +31 -4
  59. package/dist/orchestration/finalize.d.ts +1 -0
  60. package/dist/orchestration/finalize.js +5 -3
  61. package/dist/orchestration/report.js +9 -1
  62. package/dist/orchestration/worker.d.ts +1 -1
  63. package/dist/orchestration/worker.js +58 -8
  64. package/dist/rules/templates.js +3 -6
  65. package/dist/state/tasks.d.ts +12 -0
  66. package/dist/state/tasks.js +7 -0
  67. package/package.json +23 -2
  68. package/rules/defaults/README.md +9 -9
  69. package/rules/defaults/{back-zig.md → backend-agent.md} +4 -4
  70. package/rules/defaults/{front-zig.md → frontend-agent.md} +4 -4
  71. package/rules/defaults/orchestrator-agent.md +261 -0
  72. package/rules/defaults/{qa-zig.md → qa-agent.md} +11 -11
  73. package/rules/defaults/{sec-zig.md → security-agent.md} +4 -4
  74. package/rules/defaults/{sys-zig.md → system-agent.md} +8 -9
  75. package/rules/defaults/worker-common.md +25 -19
  76. package/skills/zigrix-doctor/SKILL.md +4 -2
  77. package/skills/zigrix-evidence/SKILL.md +7 -3
  78. package/skills/zigrix-main-agent-guide/SKILL.md +128 -0
  79. package/skills/zigrix-shared/SKILL.md +27 -3
  80. package/skills/zigrix-task-create/SKILL.md +8 -2
  81. package/skills/zigrix-task-status/SKILL.md +5 -2
  82. package/skills/zigrix-worker/SKILL.md +12 -4
  83. package/dist/dashboard/.next/static/chunks/app/page-25f54e54e74fb3af.js +0 -1
  84. package/rules/defaults/pro-zig.md +0 -238
  85. /package/dist/dashboard/.next/static/{2a4glWei05xr4Jg0Ly6cp → PT4hYxzrqxj-Zq4ZjtKNg}/_buildManifest.js +0 -0
  86. /package/dist/dashboard/.next/static/{2a4glWei05xr4Jg0Ly6cp → PT4hYxzrqxj-Zq4ZjtKNg}/_ssgManifest.js +0 -0
@@ -1,58 +1,90 @@
1
1
  import path from 'node:path';
2
2
  import os from 'node:os';
3
- export const ZIGRIX_HOME = process.env.ZIGRIX_HOME ?? path.join(os.homedir(), '.zigrix');
4
- export const defaultConfig = {
5
- paths: {
6
- baseDir: ZIGRIX_HOME,
7
- tasksDir: path.join(ZIGRIX_HOME, 'tasks'),
8
- evidenceDir: path.join(ZIGRIX_HOME, 'evidence'),
9
- promptsDir: path.join(ZIGRIX_HOME, 'prompts'),
10
- eventsFile: path.join(ZIGRIX_HOME, 'tasks.jsonl'),
11
- indexFile: path.join(ZIGRIX_HOME, 'index.json'),
12
- runsDir: path.join(ZIGRIX_HOME, 'runs'),
13
- rulesDir: path.join(ZIGRIX_HOME, 'rules'),
14
- },
15
- workspace: {
16
- projectsBaseDir: '',
17
- },
18
- agents: {
19
- registry: {},
20
- orchestration: {
21
- participants: [],
22
- excluded: [],
3
+ export const CONFIG_FILENAME = 'zigrix.config.json';
4
+ export const LEGACY_DEFAULT_GATEWAY_URL = 'http://127.0.0.1:18789';
5
+ export function expandTilde(input) {
6
+ const homeDir = os.homedir();
7
+ if (!input)
8
+ return homeDir;
9
+ if (input === '~')
10
+ return homeDir;
11
+ if (input.startsWith('~/'))
12
+ return path.join(homeDir, input.slice(2));
13
+ return input;
14
+ }
15
+ export function resolveAbsolutePath(input) {
16
+ return path.resolve(expandTilde(input));
17
+ }
18
+ export function resolveCanonicalConfigHome() {
19
+ return path.join(os.homedir(), '.zigrix');
20
+ }
21
+ export function resolveCanonicalConfigPath() {
22
+ return path.join(resolveCanonicalConfigHome(), CONFIG_FILENAME);
23
+ }
24
+ export function resolveDefaultWorkspaceDir(baseDir = resolveCanonicalConfigHome()) {
25
+ return path.join(baseDir, 'workspace');
26
+ }
27
+ export function buildDefaultConfig(baseDir = resolveCanonicalConfigHome()) {
28
+ return {
29
+ paths: {
30
+ baseDir,
31
+ tasksDir: path.join(baseDir, 'tasks'),
32
+ evidenceDir: path.join(baseDir, 'evidence'),
33
+ promptsDir: path.join(baseDir, 'prompts'),
34
+ eventsFile: path.join(baseDir, 'tasks.jsonl'),
35
+ indexFile: path.join(baseDir, 'index.json'),
36
+ runsDir: path.join(baseDir, 'runs'),
37
+ rulesDir: path.join(baseDir, 'rules'),
23
38
  },
24
- },
25
- rules: {
26
- scales: {
27
- simple: { requiredRoles: ['orchestrator'], optionalRoles: ['qa'] },
28
- normal: { requiredRoles: ['orchestrator', 'qa'], optionalRoles: ['frontend', 'backend'] },
29
- risky: { requiredRoles: ['orchestrator', 'qa', 'security'], optionalRoles: ['frontend', 'backend', 'infra'] },
39
+ workspace: {
40
+ projectsBaseDir: resolveDefaultWorkspaceDir(baseDir),
30
41
  },
31
- completion: {
32
- requireQa: true,
33
- requireEvidence: true,
34
- requireUserReport: true,
42
+ agents: {
43
+ registry: {},
44
+ orchestration: {
45
+ participants: [],
46
+ excluded: [],
47
+ orchestratorId: 'orchestrator',
48
+ },
35
49
  },
36
- stale: {
37
- defaultHours: 24,
50
+ rules: {
51
+ scales: {
52
+ simple: { requiredRoles: ['orchestrator'], optionalRoles: ['qa'] },
53
+ normal: { requiredRoles: ['orchestrator', 'qa'], optionalRoles: ['frontend', 'backend'] },
54
+ risky: { requiredRoles: ['orchestrator', 'qa', 'security'], optionalRoles: ['frontend', 'backend', 'system'] },
55
+ },
56
+ completion: {
57
+ requireQa: true,
58
+ requireEvidence: true,
59
+ requireUserReport: true,
60
+ },
61
+ stale: {
62
+ defaultHours: 24,
63
+ },
38
64
  },
39
- },
40
- templates: {
41
- workerPrompt: {
42
- format: 'markdown',
43
- version: 1,
44
- placeholders: ['taskId', 'title', 'scale', 'agentId', 'description'],
45
- body: '## Worker Assignment: {{taskId}}\n- title: {{title}}\n- scale: {{scale}}\n- agent: {{agentId}}\n- description: {{description}}',
65
+ templates: {
66
+ workerPrompt: {
67
+ format: 'markdown',
68
+ version: 2,
69
+ placeholders: ['taskId', 'title', 'scale', 'agentId', 'description'],
70
+ body: '## Worker Assignment: {{taskId}}\n- title: {{title}}\n- scale: {{scale}}\n- agent: {{agentId}}\n- description: {{description}}\n\n### Completion\n작업 완료 후 반드시 증적을 먼저 수집하라:\n```bash\nzigrix evidence collect --task-id {{taskId}} --agent-id {{agentId}} --summary "<결과 요약>"\n```\n⚠️ 증적 없이 완료하면 finalize에서 incomplete 판정된다.',
71
+ },
72
+ finalReport: {
73
+ format: 'markdown',
74
+ version: 1,
75
+ placeholders: ['taskId', 'title', 'status', 'summary'],
76
+ body: '## Final Report: {{taskId}}\n- title: {{title}}\n- status: {{status}}\n- summary: {{summary}}',
77
+ },
46
78
  },
47
- finalReport: {
48
- format: 'markdown',
49
- version: 1,
50
- placeholders: ['taskId', 'title', 'status', 'summary'],
51
- body: '## Final Report: {{taskId}}\n- title: {{title}}\n- status: {{status}}\n- summary: {{summary}}',
79
+ openclaw: {
80
+ home: '',
81
+ binPath: null,
82
+ gatewayUrl: '',
52
83
  },
53
- },
54
- runtime: {
55
- outputMode: 'text',
56
- jsonIndent: 2,
57
- },
58
- };
84
+ runtime: {
85
+ outputMode: 'text',
86
+ jsonIndent: 2,
87
+ },
88
+ };
89
+ }
90
+ export const defaultConfig = buildDefaultConfig();
@@ -1,13 +1,15 @@
1
+ import { CONFIG_FILENAME } from './defaults.js';
1
2
  import { type ZigrixConfig } from './schema.js';
2
3
  export type LoadedConfig = {
3
4
  config: ZigrixConfig;
4
- configPath: string | null;
5
+ configPath: string;
5
6
  baseDir: string;
6
7
  };
8
+ export declare function normalizeConfig(input: ZigrixConfig): ZigrixConfig;
7
9
  export declare function loadConfig(options?: {
8
- baseDir?: string;
9
10
  configPath?: string;
10
11
  }): LoadedConfig;
11
12
  export declare function writeConfigFile(targetPath: string, config: ZigrixConfig): string;
12
- export declare function writeDefaultConfig(baseDir?: string, force?: boolean): string;
13
+ export declare function writeDefaultConfig(force?: boolean): string;
13
14
  export declare function getConfigValue(config: ZigrixConfig, dottedPath?: string): unknown;
15
+ export { CONFIG_FILENAME };
@@ -1,14 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import process from 'node:process';
4
- import YAML from 'yaml';
5
- import { defaultConfig, ZIGRIX_HOME } from './defaults.js';
4
+ import { buildDefaultConfig, CONFIG_FILENAME, expandTilde, resolveAbsolutePath, resolveCanonicalConfigHome, resolveCanonicalConfigPath, resolveDefaultWorkspaceDir, } from './defaults.js';
6
5
  import { zigrixConfigSchema } from './schema.js';
7
- const CONFIG_CANDIDATES = [
8
- 'zigrix.config.json',
9
- 'zigrix.config.yaml',
10
- 'zigrix.config.yml',
11
- ];
12
6
  function isObject(value) {
13
7
  return typeof value === 'object' && value !== null && !Array.isArray(value);
14
8
  }
@@ -30,22 +24,60 @@ function deepMerge(base, override) {
30
24
  }
31
25
  function parseConfigFile(filePath) {
32
26
  const raw = fs.readFileSync(filePath, 'utf8');
33
- if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
34
- return YAML.parse(raw);
35
- }
36
27
  return JSON.parse(raw);
37
28
  }
38
- function resolveConfigPath(baseDir, explicitPath) {
29
+ function resolveConfigPath(explicitPath) {
39
30
  if (explicitPath) {
40
- return path.resolve(explicitPath);
31
+ return resolveAbsolutePath(explicitPath);
32
+ }
33
+ return resolveCanonicalConfigPath();
34
+ }
35
+ function resolvePathLike(value, baseDir, fallback) {
36
+ if (typeof value !== 'string')
37
+ return fallback;
38
+ const trimmed = value.trim();
39
+ if (!trimmed)
40
+ return fallback;
41
+ const expanded = expandTilde(trimmed);
42
+ if (path.isAbsolute(expanded))
43
+ return path.resolve(expanded);
44
+ return path.resolve(baseDir, expanded);
45
+ }
46
+ function normalizeConfigPaths(config) {
47
+ const copy = structuredClone(config);
48
+ const configHome = resolveCanonicalConfigHome();
49
+ const resolvedBaseDir = resolvePathLike(copy.paths.baseDir, configHome, configHome);
50
+ copy.paths.baseDir = resolvedBaseDir;
51
+ copy.paths.tasksDir = resolvePathLike(copy.paths.tasksDir, resolvedBaseDir, path.join(resolvedBaseDir, 'tasks'));
52
+ copy.paths.evidenceDir = resolvePathLike(copy.paths.evidenceDir, resolvedBaseDir, path.join(resolvedBaseDir, 'evidence'));
53
+ copy.paths.promptsDir = resolvePathLike(copy.paths.promptsDir, resolvedBaseDir, path.join(resolvedBaseDir, 'prompts'));
54
+ copy.paths.eventsFile = resolvePathLike(copy.paths.eventsFile, resolvedBaseDir, path.join(resolvedBaseDir, 'tasks.jsonl'));
55
+ copy.paths.indexFile = resolvePathLike(copy.paths.indexFile, resolvedBaseDir, path.join(resolvedBaseDir, 'index.json'));
56
+ copy.paths.runsDir = resolvePathLike(copy.paths.runsDir, resolvedBaseDir, path.join(resolvedBaseDir, 'runs'));
57
+ copy.paths.rulesDir = resolvePathLike(copy.paths.rulesDir, resolvedBaseDir, path.join(resolvedBaseDir, 'rules'));
58
+ const configuredWorkspace = typeof copy.workspace.projectsBaseDir === 'string'
59
+ ? copy.workspace.projectsBaseDir.trim()
60
+ : '';
61
+ copy.workspace.projectsBaseDir = configuredWorkspace.length > 0
62
+ ? resolvePathLike(configuredWorkspace, resolvedBaseDir, resolveDefaultWorkspaceDir(resolvedBaseDir))
63
+ : resolveDefaultWorkspaceDir(resolvedBaseDir);
64
+ if (typeof copy.openclaw.home === 'string' && copy.openclaw.home.trim().length > 0) {
65
+ copy.openclaw.home = resolveAbsolutePath(copy.openclaw.home);
41
66
  }
42
- for (const candidate of CONFIG_CANDIDATES) {
43
- const fullPath = path.join(baseDir, candidate);
44
- if (fs.existsSync(fullPath)) {
45
- return fullPath;
67
+ if (typeof copy.openclaw.binPath === 'string' && copy.openclaw.binPath.trim().length > 0) {
68
+ copy.openclaw.binPath = resolveAbsolutePath(copy.openclaw.binPath);
69
+ }
70
+ return copy;
71
+ }
72
+ function resolveBaseDirHint(parsed, fallback) {
73
+ if (isObject(parsed) && isObject(parsed.paths) && typeof parsed.paths.baseDir === 'string') {
74
+ const configured = parsed.paths.baseDir.trim();
75
+ if (configured.length > 0) {
76
+ const expanded = expandTilde(configured);
77
+ return path.isAbsolute(expanded) ? path.resolve(expanded) : path.resolve(fallback, expanded);
46
78
  }
47
79
  }
48
- return null;
80
+ return fallback;
49
81
  }
50
82
  function applyEnvOverrides(config) {
51
83
  const copy = structuredClone(config);
@@ -60,29 +92,35 @@ function applyEnvOverrides(config) {
60
92
  }
61
93
  return copy;
62
94
  }
95
+ export function normalizeConfig(input) {
96
+ const normalized = normalizeConfigPaths(input);
97
+ return zigrixConfigSchema.parse(normalized);
98
+ }
63
99
  export function loadConfig(options) {
64
- const baseDir = path.resolve(options?.baseDir ?? ZIGRIX_HOME);
65
- const configPath = resolveConfigPath(baseDir, options?.configPath);
66
- const parsed = configPath ? parseConfigFile(configPath) : {};
67
- const merged = deepMerge(structuredClone(defaultConfig), parsed);
100
+ const configPath = resolveConfigPath(options?.configPath);
101
+ const parsed = fs.existsSync(configPath) ? parseConfigFile(configPath) : {};
102
+ const defaultBaseDir = resolveBaseDirHint(parsed, resolveCanonicalConfigHome());
103
+ const defaults = buildDefaultConfig(defaultBaseDir);
104
+ const merged = deepMerge(structuredClone(defaults), parsed);
68
105
  const withEnv = applyEnvOverrides(merged);
69
- const result = zigrixConfigSchema.parse(withEnv);
70
- return { config: result, configPath, baseDir };
106
+ const result = normalizeConfig(withEnv);
107
+ return { config: result, configPath, baseDir: result.paths.baseDir };
71
108
  }
72
109
  export function writeConfigFile(targetPath, config) {
73
- const resolvedPath = path.resolve(targetPath);
110
+ const resolvedPath = resolveAbsolutePath(targetPath);
74
111
  fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
75
- fs.writeFileSync(resolvedPath, `${JSON.stringify(zigrixConfigSchema.parse(config), null, 2)}\n`, 'utf8');
112
+ const normalized = normalizeConfig(config);
113
+ fs.writeFileSync(resolvedPath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8');
76
114
  return resolvedPath;
77
115
  }
78
- export function writeDefaultConfig(baseDir, force = false) {
79
- const resolvedBase = path.resolve(baseDir ?? ZIGRIX_HOME);
80
- const targetPath = path.join(resolvedBase, 'zigrix.config.json');
116
+ export function writeDefaultConfig(force = false) {
117
+ const configHome = resolveCanonicalConfigHome();
118
+ const targetPath = resolveCanonicalConfigPath();
81
119
  if (fs.existsSync(targetPath) && !force) {
82
120
  throw new Error(`config already exists: ${targetPath}`);
83
121
  }
84
- fs.mkdirSync(resolvedBase, { recursive: true });
85
- return writeConfigFile(targetPath, structuredClone(defaultConfig));
122
+ fs.mkdirSync(configHome, { recursive: true });
123
+ return writeConfigFile(targetPath, buildDefaultConfig(configHome));
86
124
  }
87
125
  export function getConfigValue(config, dottedPath) {
88
126
  if (!dottedPath)
@@ -94,3 +132,4 @@ export function getConfigValue(config, dottedPath) {
94
132
  return undefined;
95
133
  }, config);
96
134
  }
135
+ export { CONFIG_FILENAME };
@@ -16,7 +16,14 @@ export declare const zigrixConfigSchema: z.ZodObject<{
16
16
  agents: z.ZodObject<{
17
17
  registry: z.ZodRecord<z.ZodString, z.ZodObject<{
18
18
  label: z.ZodString;
19
- role: z.ZodString;
19
+ role: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<"orchestrator" | "qa" | "security" | "frontend" | "backend" | "system", string>>, z.ZodEnum<{
20
+ orchestrator: "orchestrator";
21
+ qa: "qa";
22
+ security: "security";
23
+ frontend: "frontend";
24
+ backend: "backend";
25
+ system: "system";
26
+ }>>;
20
27
  runtime: z.ZodString;
21
28
  enabled: z.ZodDefault<z.ZodBoolean>;
22
29
  metadata: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -24,12 +31,27 @@ export declare const zigrixConfigSchema: z.ZodObject<{
24
31
  orchestration: z.ZodObject<{
25
32
  participants: z.ZodArray<z.ZodString>;
26
33
  excluded: z.ZodArray<z.ZodString>;
34
+ orchestratorId: z.ZodString;
27
35
  }, z.core.$strip>;
28
36
  }, z.core.$strip>;
29
37
  rules: z.ZodObject<{
30
38
  scales: z.ZodRecord<z.ZodString, z.ZodObject<{
31
- requiredRoles: z.ZodArray<z.ZodString>;
32
- optionalRoles: z.ZodArray<z.ZodString>;
39
+ requiredRoles: z.ZodArray<z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<"orchestrator" | "qa" | "security" | "frontend" | "backend" | "system", string>>, z.ZodEnum<{
40
+ orchestrator: "orchestrator";
41
+ qa: "qa";
42
+ security: "security";
43
+ frontend: "frontend";
44
+ backend: "backend";
45
+ system: "system";
46
+ }>>>;
47
+ optionalRoles: z.ZodArray<z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<"orchestrator" | "qa" | "security" | "frontend" | "backend" | "system", string>>, z.ZodEnum<{
48
+ orchestrator: "orchestrator";
49
+ qa: "qa";
50
+ security: "security";
51
+ frontend: "frontend";
52
+ backend: "backend";
53
+ system: "system";
54
+ }>>>;
33
55
  }, z.core.$strip>>;
34
56
  completion: z.ZodObject<{
35
57
  requireQa: z.ZodBoolean;
@@ -60,6 +82,11 @@ export declare const zigrixConfigSchema: z.ZodObject<{
60
82
  body: z.ZodString;
61
83
  }, z.core.$strip>;
62
84
  }, z.core.$strip>;
85
+ openclaw: z.ZodDefault<z.ZodObject<{
86
+ home: z.ZodDefault<z.ZodString>;
87
+ binPath: z.ZodDefault<z.ZodNullable<z.ZodString>>;
88
+ gatewayUrl: z.ZodDefault<z.ZodString>;
89
+ }, z.core.$strip>>;
63
90
  runtime: z.ZodObject<{
64
91
  outputMode: z.ZodEnum<{
65
92
  text: "text";
@@ -73,7 +100,7 @@ export declare const zigrixConfigJsonSchema: {
73
100
  readonly $schema: "https://json-schema.org/draft/2020-12/schema";
74
101
  readonly title: "ZigrixConfig";
75
102
  readonly type: "object";
76
- readonly required: readonly ["paths", "workspace", "agents", "rules", "templates", "runtime"];
103
+ readonly required: readonly ["paths", "workspace", "agents", "rules", "templates", "openclaw", "runtime"];
77
104
  readonly properties: {
78
105
  readonly paths: {
79
106
  readonly type: "object";
@@ -124,6 +151,21 @@ export declare const zigrixConfigJsonSchema: {
124
151
  readonly templates: {
125
152
  readonly type: "object";
126
153
  };
154
+ readonly openclaw: {
155
+ readonly type: "object";
156
+ readonly properties: {
157
+ readonly home: {
158
+ readonly type: "string";
159
+ };
160
+ readonly binPath: {
161
+ readonly type: readonly ["string", "null"];
162
+ };
163
+ readonly gatewayUrl: {
164
+ readonly type: "string";
165
+ };
166
+ };
167
+ readonly additionalProperties: false;
168
+ };
127
169
  readonly runtime: {
128
170
  readonly type: "object";
129
171
  };
@@ -1,9 +1,21 @@
1
1
  import { z } from 'zod';
2
- const roleSchema = z.string().min(1);
2
+ import { STANDARD_AGENT_ROLES, normalizeAgentRole } from '../agents/roles.js';
3
3
  const pathSchema = z.string().min(1);
4
+ const standardRoleSchema = z.enum(STANDARD_AGENT_ROLES);
5
+ const roleSchema = z.string().min(1).transform((value, ctx) => {
6
+ const normalized = normalizeAgentRole(value);
7
+ if (!normalized) {
8
+ ctx.addIssue({
9
+ code: z.ZodIssueCode.custom,
10
+ message: `role must be one of: ${STANDARD_AGENT_ROLES.join(', ')}`,
11
+ });
12
+ return z.NEVER;
13
+ }
14
+ return normalized;
15
+ }).pipe(standardRoleSchema);
4
16
  const agentSchema = z.object({
5
17
  label: z.string().min(1),
6
- role: z.string().min(1),
18
+ role: roleSchema,
7
19
  runtime: z.string().min(1),
8
20
  enabled: z.boolean().default(true),
9
21
  metadata: z.record(z.string(), z.unknown()).default({}),
@@ -33,6 +45,7 @@ export const zigrixConfigSchema = z.object({
33
45
  orchestration: z.object({
34
46
  participants: z.array(z.string()),
35
47
  excluded: z.array(z.string()),
48
+ orchestratorId: z.string().min(1),
36
49
  }),
37
50
  }).superRefine((value, ctx) => {
38
51
  const overlap = value.orchestration.participants.filter((item) => value.orchestration.excluded.includes(item));
@@ -62,6 +75,25 @@ export const zigrixConfigSchema = z.object({
62
75
  });
63
76
  }
64
77
  }
78
+ // Only validate orchestratorId against registry when agents are registered
79
+ // During bootstrap (addAgent one-by-one), registry may not yet include the orchestrator
80
+ const orchestratorId = value.orchestration.orchestratorId;
81
+ const hasOrchestratorInRegistry = knownAgents.has(orchestratorId);
82
+ const hasAnyOrchestrator = Object.values(value.registry).some((agent) => agent.role === 'orchestrator');
83
+ if (hasAnyOrchestrator && !hasOrchestratorInRegistry) {
84
+ ctx.addIssue({
85
+ code: z.ZodIssueCode.custom,
86
+ message: `orchestratorId '${orchestratorId}' must exist in registry`,
87
+ path: ['orchestration', 'orchestratorId'],
88
+ });
89
+ }
90
+ if (value.orchestration.excluded.includes(orchestratorId)) {
91
+ ctx.addIssue({
92
+ code: z.ZodIssueCode.custom,
93
+ message: `orchestratorId '${orchestratorId}' cannot be excluded`,
94
+ path: ['orchestration', 'orchestratorId'],
95
+ });
96
+ }
65
97
  }),
66
98
  rules: z.object({
67
99
  scales: z.record(z.string(), z.object({
@@ -81,6 +113,11 @@ export const zigrixConfigSchema = z.object({
81
113
  workerPrompt: templateSchema,
82
114
  finalReport: templateSchema,
83
115
  }),
116
+ openclaw: z.object({
117
+ home: z.string().default(''),
118
+ binPath: z.string().nullable().default(null),
119
+ gatewayUrl: z.string().default(''),
120
+ }).default({ home: '', binPath: null, gatewayUrl: '' }),
84
121
  runtime: z.object({
85
122
  outputMode: z.enum(['text', 'json']),
86
123
  jsonIndent: z.number().int().min(0).max(8),
@@ -90,7 +127,7 @@ export const zigrixConfigJsonSchema = {
90
127
  $schema: 'https://json-schema.org/draft/2020-12/schema',
91
128
  title: 'ZigrixConfig',
92
129
  type: 'object',
93
- required: ['paths', 'workspace', 'agents', 'rules', 'templates', 'runtime'],
130
+ required: ['paths', 'workspace', 'agents', 'rules', 'templates', 'openclaw', 'runtime'],
94
131
  properties: {
95
132
  paths: {
96
133
  type: 'object',
@@ -117,6 +154,15 @@ export const zigrixConfigJsonSchema = {
117
154
  agents: { type: 'object' },
118
155
  rules: { type: 'object' },
119
156
  templates: { type: 'object' },
157
+ openclaw: {
158
+ type: 'object',
159
+ properties: {
160
+ home: { type: 'string' },
161
+ binPath: { type: ['string', 'null'] },
162
+ gatewayUrl: { type: 'string' },
163
+ },
164
+ additionalProperties: false,
165
+ },
120
166
  runtime: { type: 'object' },
121
167
  },
122
168
  additionalProperties: false,
@@ -11,6 +11,7 @@ export interface ConfigureResult {
11
11
  rulesSkipped: string[];
12
12
  skillsResult: SkillRegistrationResult | null;
13
13
  pathResult: PathStabilizeResult | null;
14
+ openclawPathResult: PathStabilizeResult | null;
14
15
  workspaceChanged: boolean;
15
16
  warnings: string[];
16
17
  }
@@ -19,6 +20,7 @@ export interface RunConfigureOptions {
19
20
  yes?: boolean;
20
21
  projectDir?: string;
21
22
  projectsBaseDir?: string;
23
+ orchestratorId?: string;
22
24
  silent?: boolean;
23
25
  }
24
26
  export declare function runConfigure(options: RunConfigureOptions): Promise<ConfigureResult>;
package/dist/configure.js CHANGED
@@ -1,9 +1,8 @@
1
1
  import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
2
  import { removeAgent } from './agents/registry.js';
3
+ import { resolveAbsolutePath } from './config/defaults.js';
5
4
  import { loadConfig, writeConfigFile } from './config/load.js';
6
- import { detectOpenClawHome, ensureZigrixInPath, filterAgents, loadOpenClawConfig, promptAgentSelection, registerAgents, registerSkills, seedRules, } from './onboard.js';
5
+ import { detectOpenClawHome, ensureOpenClawInPath, ensureZigrixInPath, filterAgents, loadOpenClawConfig, promptAgentSelection, ensureOrchestratorId, registerAgents, registerSkills, resolveRuleSeedSource, seedRules, } from './onboard.js';
7
6
  import { resolvePaths } from './state/paths.js';
8
7
  const ALL_SECTIONS = ['agents', 'rules', 'workspace', 'path', 'skills'];
9
8
  // ─── Configure ────────────────────────────────────────────────────────────────
@@ -12,11 +11,7 @@ export async function runConfigure(options) {
12
11
  const silent = options.silent ?? false;
13
12
  const log = (msg) => { if (!silent)
14
13
  console.log(msg); };
15
- // Resolve base dir at runtime (not from the static ZIGRIX_HOME constant)
16
- const runtimeBaseDir = process.env.ZIGRIX_HOME
17
- ? path.resolve(process.env.ZIGRIX_HOME)
18
- : path.join(os.homedir(), '.zigrix');
19
- const loaded = loadConfig({ baseDir: runtimeBaseDir });
14
+ const loaded = loadConfig();
20
15
  if (!loaded.configPath || !fs.existsSync(loaded.configPath)) {
21
16
  throw new Error('zigrix not initialized. Run `zigrix onboard` first.');
22
17
  }
@@ -33,6 +28,7 @@ export async function runConfigure(options) {
33
28
  let rulesSkipped = [];
34
29
  let skillsResult = null;
35
30
  let pathResult = null;
31
+ let openclawPathResult = null;
36
32
  let workspaceChanged = false;
37
33
  let configDirty = false;
38
34
  // ─── agents ───────────────────────────────────────────────────────────
@@ -81,9 +77,22 @@ export async function runConfigure(options) {
81
77
  log(`⚠️ ${warnings[warnings.length - 1]}`);
82
78
  }
83
79
  }
80
+ // ─── orchestrator ────────────────────────────────────────────────────
81
+ if (sections.includes('agents') || options.orchestratorId) {
82
+ const orchResult = ensureOrchestratorId(config, options.orchestratorId);
83
+ config = orchResult.config;
84
+ if (orchResult.changed) {
85
+ configDirty = true;
86
+ log(`✅ Orchestrator set to: ${config.agents.orchestration.orchestratorId}`);
87
+ }
88
+ if (orchResult.warning) {
89
+ warnings.push(orchResult.warning);
90
+ log(`⚠️ ${orchResult.warning}`);
91
+ }
92
+ }
84
93
  // ─── workspace ────────────────────────────────────────────────────────
85
94
  if (sections.includes('workspace') && options.projectsBaseDir) {
86
- const resolved = path.resolve(options.projectsBaseDir);
95
+ const resolved = resolveAbsolutePath(options.projectsBaseDir);
87
96
  if (config.workspace.projectsBaseDir !== resolved) {
88
97
  config.workspace.projectsBaseDir = resolved;
89
98
  configDirty = true;
@@ -93,10 +102,12 @@ export async function runConfigure(options) {
93
102
  }
94
103
  // ─── rules ────────────────────────────────────────────────────────────
95
104
  if (sections.includes('rules')) {
96
- const projectDir = options.projectDir ?? process.cwd();
97
- const rulesSourceDir = path.join(projectDir, 'orchestration', 'rules');
98
- if (fs.existsSync(rulesSourceDir)) {
99
- const result = seedRules(rulesSourceDir, paths.rulesDir);
105
+ const ruleSeed = resolveRuleSeedSource(options.projectDir);
106
+ if (ruleSeed.sourceDir) {
107
+ if (options.projectDir && ruleSeed.source === 'bundled') {
108
+ log(`ℹ️ No external rule templates found under ${options.projectDir}; using bundled defaults.`);
109
+ }
110
+ const result = seedRules(ruleSeed.sourceDir, paths.rulesDir);
100
111
  rulesCopied = result.copied;
101
112
  rulesSkipped = result.skipped;
102
113
  if (rulesCopied.length > 0)
@@ -105,7 +116,7 @@ export async function runConfigure(options) {
105
116
  log(`⏭️ Rules unchanged: ${rulesSkipped.join(', ')}`);
106
117
  }
107
118
  else {
108
- log(`ℹ️ No orchestration/rules/ at ${projectDir} — rule seeding skipped.`);
119
+ log('ℹ️ No bundled rule templates available — rule seeding skipped.');
109
120
  }
110
121
  }
111
122
  // ─── path ─────────────────────────────────────────────────────────────
@@ -121,6 +132,17 @@ export async function runConfigure(options) {
121
132
  warnings.push(pathResult.warning);
122
133
  log(`⚠️ ${pathResult.warning}`);
123
134
  }
135
+ openclawPathResult = ensureOpenClawInPath();
136
+ if (openclawPathResult.symlinkCreated) {
137
+ log(`✅ openclaw symlinked to ${openclawPathResult.symlinkPath}`);
138
+ }
139
+ else if (openclawPathResult.alreadyInPath) {
140
+ log('✅ openclaw already in PATH');
141
+ }
142
+ if (openclawPathResult.warning) {
143
+ warnings.push(openclawPathResult.warning);
144
+ log(`⚠️ ${openclawPathResult.warning}`);
145
+ }
124
146
  }
125
147
  // ─── skills ───────────────────────────────────────────────────────────
126
148
  if (sections.includes('skills')) {
@@ -158,6 +180,7 @@ export async function runConfigure(options) {
158
180
  rulesSkipped,
159
181
  skillsResult,
160
182
  pathResult,
183
+ openclawPathResult,
161
184
  workspaceChanged,
162
185
  warnings,
163
186
  };
@@ -1 +1 @@
1
- 2a4glWei05xr4Jg0Ly6cp
1
+ PT4hYxzrqxj-Zq4ZjtKNg