zigrix 0.1.1 → 0.2.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 (48) hide show
  1. package/dist/config/defaults.d.ts +8 -0
  2. package/dist/config/defaults.js +8 -1
  3. package/dist/config/schema.d.ts +112 -0
  4. package/dist/config/schema.js +186 -12
  5. package/dist/dashboard/.next/BUILD_ID +1 -1
  6. package/dist/dashboard/.next/app-build-manifest.json +10 -10
  7. package/dist/dashboard/.next/app-path-routes-manifest.json +2 -2
  8. package/dist/dashboard/.next/build-manifest.json +2 -2
  9. package/dist/dashboard/.next/prerender-manifest.json +6 -6
  10. package/dist/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  11. package/dist/dashboard/.next/server/app/_not-found.html +1 -1
  12. package/dist/dashboard/.next/server/app/_not-found.rsc +1 -1
  13. package/dist/dashboard/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  14. package/dist/dashboard/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  15. package/dist/dashboard/.next/server/app/api/auth/session/route_client-reference-manifest.js +1 -1
  16. package/dist/dashboard/.next/server/app/api/auth/setup/route_client-reference-manifest.js +1 -1
  17. package/dist/dashboard/.next/server/app/api/overview/route_client-reference-manifest.js +1 -1
  18. package/dist/dashboard/.next/server/app/api/stream/route_client-reference-manifest.js +1 -1
  19. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  20. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/conversation/route_client-reference-manifest.js +1 -1
  21. package/dist/dashboard/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  22. package/dist/dashboard/.next/server/app/login/page_client-reference-manifest.js +1 -1
  23. package/dist/dashboard/.next/server/app/login.html +1 -1
  24. package/dist/dashboard/.next/server/app/login.rsc +1 -1
  25. package/dist/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  26. package/dist/dashboard/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  27. package/dist/dashboard/.next/server/app/setup.html +1 -1
  28. package/dist/dashboard/.next/server/app/setup.rsc +1 -1
  29. package/dist/dashboard/.next/server/app-paths-manifest.json +2 -2
  30. package/dist/dashboard/.next/server/functions-config-manifest.json +2 -2
  31. package/dist/dashboard/.next/server/pages/404.html +1 -1
  32. package/dist/dashboard/.next/server/pages/500.html +1 -1
  33. package/dist/doctor.d.ts +3 -0
  34. package/dist/doctor.js +233 -60
  35. package/dist/index.js +262 -32
  36. package/dist/migrate/import-orchestration.d.ts +31 -0
  37. package/dist/migrate/import-orchestration.js +638 -0
  38. package/dist/onboard.js +130 -35
  39. package/dist/orchestration/evidence.d.ts +7 -0
  40. package/dist/orchestration/evidence.js +79 -4
  41. package/dist/orchestration/pipeline.d.ts +1 -0
  42. package/dist/orchestration/pipeline.js +26 -1
  43. package/dist/state/tasks.d.ts +37 -2
  44. package/dist/state/tasks.js +242 -10
  45. package/dist/state/verify.js +89 -11
  46. package/package.json +1 -1
  47. /package/dist/dashboard/.next/static/{iKGx5hWe1zbwJZWchF9kg → EZjkAnODdTglaMXuBw76E}/_buildManifest.js +0 -0
  48. /package/dist/dashboard/.next/static/{iKGx5hWe1zbwJZWchF9kg → EZjkAnODdTglaMXuBw76E}/_ssgManifest.js +0 -0
package/dist/doctor.js CHANGED
@@ -1,34 +1,128 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { STANDARD_AGENT_ROLES } from './agents/roles.js';
4
+ import { resolveCanonicalConfigHome, resolveCanonicalConfigPath } from './config/defaults.js';
5
+ import { STANDARD_SCALES } from './config/schema.js';
6
+ const BASELINE_REQUIRED_ROLES = ['orchestrator', 'qa'];
7
+ function unique(items) {
8
+ return [...new Set(items)];
9
+ }
3
10
  function existsWritable(targetPath) {
4
11
  try {
5
- return fs.existsSync(targetPath) ? fs.accessSync(targetPath, fs.constants.W_OK) === undefined : fs.accessSync(path.dirname(targetPath), fs.constants.W_OK) === undefined;
12
+ return fs.existsSync(targetPath)
13
+ ? fs.accessSync(targetPath, fs.constants.W_OK) === undefined
14
+ : fs.accessSync(path.dirname(targetPath), fs.constants.W_OK) === undefined;
6
15
  }
7
16
  catch {
8
17
  return false;
9
18
  }
10
19
  }
11
20
  function detectOpenClawHome() {
12
- return process.env.OPENCLAW_HOME ? path.resolve(process.env.OPENCLAW_HOME) : path.join(process.env.HOME ?? '~', '.openclaw');
21
+ return process.env.OPENCLAW_HOME
22
+ ? path.resolve(process.env.OPENCLAW_HOME)
23
+ : path.join(process.env.HOME ?? '~', '.openclaw');
13
24
  }
14
- export function gatherDoctor(loaded, paths) {
15
- const openclawHome = detectOpenClawHome();
16
- const openclawSkillsDir = path.join(openclawHome, 'skills');
17
- const warnings = [];
18
- const rulesDir = paths.rulesDir;
19
- const ruleFiles = fs.existsSync(rulesDir)
20
- ? fs.readdirSync(rulesDir).filter((f) => f.endsWith('.md')).sort()
21
- : [];
22
- // Non-login shell PATH reachability check
23
- const nonLoginShellPaths = ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin'];
24
- const zigrixInNonLoginPath = nonLoginShellPaths.some((dir) => {
25
- try {
26
- return fs.existsSync(path.join(dir, 'zigrix'));
25
+ function resolveEligibleAgentsByRole(config) {
26
+ const participants = new Set(config.agents.orchestration.participants);
27
+ const excluded = new Set(config.agents.orchestration.excluded);
28
+ const participantMode = participants.size > 0;
29
+ const byRole = Object.fromEntries(STANDARD_AGENT_ROLES.map((role) => [role, []]));
30
+ for (const [agentId, agent] of Object.entries(config.agents.registry).sort(([a], [b]) => a.localeCompare(b))) {
31
+ if (!agent.enabled)
32
+ continue;
33
+ if (excluded.has(agentId))
34
+ continue;
35
+ if (participantMode && !participants.has(agentId))
36
+ continue;
37
+ byRole[agent.role] = [...(byRole[agent.role] ?? []), agentId];
38
+ }
39
+ return byRole;
40
+ }
41
+ function collectPathConflicts(paths) {
42
+ const issues = [];
43
+ const entries = [
44
+ ['tasksDir', paths.tasksDir],
45
+ ['evidenceDir', paths.evidenceDir],
46
+ ['promptsDir', paths.promptsDir],
47
+ ['runsDir', paths.runsDir],
48
+ ['rulesDir', paths.rulesDir],
49
+ ['eventsFile', paths.eventsFile],
50
+ ['indexFile', paths.indexFile],
51
+ ];
52
+ const seen = new Map();
53
+ for (const [name, rawPath] of entries) {
54
+ const normalized = path.resolve(rawPath);
55
+ const previous = seen.get(normalized);
56
+ if (previous) {
57
+ issues.push(`${name} collides with ${previous}`);
58
+ continue;
27
59
  }
28
- catch {
29
- return false;
60
+ seen.set(normalized, name);
61
+ }
62
+ return issues;
63
+ }
64
+ function collectConfigConsistency(config, paths) {
65
+ const issues = collectPathConflicts(paths);
66
+ const warnings = [];
67
+ const missingStandardScales = STANDARD_SCALES.filter((scale) => !(scale in config.rules.scales));
68
+ const eligibleByRole = resolveEligibleAgentsByRole(config);
69
+ const participantMode = config.agents.orchestration.participants.length > 0;
70
+ const orchestratorId = config.agents.orchestration.orchestratorId;
71
+ const orchestratorAgent = config.agents.registry[orchestratorId];
72
+ const orchestratorPresent = Boolean(orchestratorAgent);
73
+ const orchestratorEnabled = orchestratorAgent?.enabled !== false;
74
+ const inParticipants = config.agents.orchestration.participants.includes(orchestratorId);
75
+ const excluded = config.agents.orchestration.excluded.includes(orchestratorId);
76
+ const orchestratorEligible = orchestratorPresent && orchestratorEnabled && !excluded && (!participantMode || inParticipants);
77
+ for (const scale of missingStandardScales) {
78
+ issues.push(`rules.scales is missing '${scale}'`);
79
+ }
80
+ if (orchestratorPresent && !orchestratorEnabled) {
81
+ issues.push(`orchestratorId '${orchestratorId}' points to a disabled agent`);
82
+ }
83
+ if (orchestratorPresent && excluded) {
84
+ issues.push(`orchestratorId '${orchestratorId}' is excluded from orchestration`);
85
+ }
86
+ if (participantMode && orchestratorPresent && !inParticipants) {
87
+ issues.push(`orchestratorId '${orchestratorId}' is not included in participants while participant mode is active`);
88
+ }
89
+ const scaleCoverage = {};
90
+ for (const scale of STANDARD_SCALES) {
91
+ const policy = config.rules.scales[scale];
92
+ if (!policy)
93
+ continue;
94
+ const requiredRoles = unique([...BASELINE_REQUIRED_ROLES, ...policy.requiredRoles]);
95
+ const missingEligibleRoles = requiredRoles.filter((role) => (eligibleByRole[role] ?? []).length === 0);
96
+ scaleCoverage[scale] = {
97
+ requiredRoles,
98
+ eligibleByRole: Object.fromEntries(requiredRoles.map((role) => [role, [...(eligibleByRole[role] ?? [])]])),
99
+ missingEligibleRoles,
100
+ };
101
+ if (missingEligibleRoles.length > 0) {
102
+ warnings.push(`scale '${scale}' has no eligible agents for required roles: ${missingEligibleRoles.join(', ')}`);
30
103
  }
31
- });
104
+ }
105
+ return {
106
+ ok: issues.length === 0,
107
+ issues,
108
+ warnings,
109
+ pathConflicts: collectPathConflicts(paths),
110
+ missingStandardScales,
111
+ orchestrator: {
112
+ id: orchestratorId,
113
+ presentInRegistry: orchestratorPresent,
114
+ enabled: orchestratorEnabled,
115
+ participantMode,
116
+ inParticipants,
117
+ excluded,
118
+ eligible: orchestratorEligible,
119
+ },
120
+ scaleCoverage,
121
+ };
122
+ }
123
+ function buildBasePayload(params) {
124
+ const openclawSkillsDir = path.join(params.openclawHome, 'skills');
125
+ const nonLoginShellPaths = ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin'];
32
126
  const zigrixNonLoginLocation = nonLoginShellPaths.find((dir) => {
33
127
  try {
34
128
  return fs.existsSync(path.join(dir, 'zigrix'));
@@ -37,14 +131,6 @@ export function gatherDoctor(loaded, paths) {
37
131
  return false;
38
132
  }
39
133
  }) ?? null;
40
- const openclawInNonLoginPath = nonLoginShellPaths.some((dir) => {
41
- try {
42
- return fs.existsSync(path.join(dir, 'openclaw'));
43
- }
44
- catch {
45
- return false;
46
- }
47
- });
48
134
  const openclawNonLoginLocation = nonLoginShellPaths.find((dir) => {
49
135
  try {
50
136
  return fs.existsSync(path.join(dir, 'openclaw'));
@@ -53,56 +139,100 @@ export function gatherDoctor(loaded, paths) {
53
139
  return false;
54
140
  }
55
141
  }) ?? null;
56
- const payload = {
142
+ return {
57
143
  node: {
58
144
  executable: process.execPath,
59
145
  version: process.versions.node,
60
146
  ok: Number(process.versions.node.split('.')[0]) >= 22,
61
147
  },
62
148
  paths: {
63
- baseDir: loaded.baseDir,
64
- configPath: loaded.configPath,
65
- tasksDir: paths.tasksDir,
66
- promptsDir: paths.promptsDir,
67
- evidenceDir: paths.evidenceDir,
68
- runsDir: paths.runsDir,
69
- rulesDir: paths.rulesDir,
149
+ baseDir: params.baseDir,
150
+ configPath: params.configPath,
151
+ tasksDir: null,
152
+ promptsDir: null,
153
+ evidenceDir: null,
154
+ runsDir: null,
155
+ rulesDir: null,
70
156
  },
71
157
  files: {
72
- configExists: loaded.configPath ? fs.existsSync(loaded.configPath) : false,
73
- baseDirExists: fs.existsSync(paths.baseDir),
74
- indexExists: fs.existsSync(paths.indexFile),
75
- eventsExists: fs.existsSync(paths.eventsFile),
158
+ configExists: fs.existsSync(params.configPath),
159
+ baseDirExists: fs.existsSync(params.baseDir),
160
+ indexExists: false,
161
+ eventsExists: false,
76
162
  },
77
163
  rules: {
78
- dir: rulesDir,
79
- exists: fs.existsSync(rulesDir),
80
- files: ruleFiles,
81
- count: ruleFiles.length,
164
+ dir: null,
165
+ exists: false,
166
+ files: [],
167
+ count: 0,
82
168
  },
83
169
  writeAccess: {
84
- baseDir: existsWritable(paths.baseDir),
85
- configPath: loaded.configPath ? existsWritable(loaded.configPath) : existsWritable(path.join(paths.baseDir, 'zigrix.config.json')),
170
+ baseDir: existsWritable(params.baseDir),
171
+ configPath: existsWritable(params.configPath),
86
172
  },
87
173
  binaries: {
88
174
  node: process.execPath,
89
175
  npm: process.env.npm_execpath ?? null,
90
- openclaw: loaded.config.openclaw?.binPath ?? null,
176
+ openclaw: null,
91
177
  },
92
178
  openclaw: {
93
- home: openclawHome,
94
- exists: fs.existsSync(openclawHome),
179
+ home: params.openclawHome,
180
+ exists: fs.existsSync(params.openclawHome),
95
181
  skillsDir: openclawSkillsDir,
96
182
  skillsDirExists: fs.existsSync(openclawSkillsDir),
97
183
  },
98
184
  pathReach: {
99
185
  nonLoginShellPaths,
100
- zigrixInNonLoginPath,
186
+ zigrixInNonLoginPath: zigrixNonLoginLocation !== null,
101
187
  zigrixNonLoginLocation,
102
- openclawInNonLoginPath,
188
+ openclawInNonLoginPath: openclawNonLoginLocation !== null,
103
189
  openclawNonLoginLocation,
104
190
  },
105
191
  };
192
+ }
193
+ export function gatherDoctor(loaded, paths) {
194
+ const openclawHome = detectOpenClawHome();
195
+ const rulesDir = paths.rulesDir;
196
+ const ruleFiles = fs.existsSync(rulesDir)
197
+ ? fs
198
+ .readdirSync(rulesDir)
199
+ .filter((f) => f.endsWith('.md'))
200
+ .sort()
201
+ : [];
202
+ const basePayload = buildBasePayload({
203
+ configPath: loaded.configPath,
204
+ baseDir: loaded.baseDir,
205
+ openclawHome,
206
+ });
207
+ const warnings = [];
208
+ const configConsistency = collectConfigConsistency(loaded.config, paths);
209
+ const payload = {
210
+ ...basePayload,
211
+ paths: {
212
+ ...basePayload.paths,
213
+ tasksDir: paths.tasksDir,
214
+ promptsDir: paths.promptsDir,
215
+ evidenceDir: paths.evidenceDir,
216
+ runsDir: paths.runsDir,
217
+ rulesDir: paths.rulesDir,
218
+ },
219
+ files: {
220
+ ...basePayload.files,
221
+ indexExists: fs.existsSync(paths.indexFile),
222
+ eventsExists: fs.existsSync(paths.eventsFile),
223
+ },
224
+ rules: {
225
+ dir: rulesDir,
226
+ exists: fs.existsSync(rulesDir),
227
+ files: ruleFiles,
228
+ count: ruleFiles.length,
229
+ },
230
+ binaries: {
231
+ ...basePayload.binaries,
232
+ openclaw: loaded.config.openclaw?.binPath ?? null,
233
+ },
234
+ configConsistency,
235
+ };
106
236
  if (!payload.node.ok)
107
237
  warnings.push('Node.js 22+ is required.');
108
238
  if (!payload.files.configExists)
@@ -113,8 +243,9 @@ export function gatherDoctor(loaded, paths) {
113
243
  warnings.push('Base directory is not writable.');
114
244
  if (!payload.openclaw.exists)
115
245
  warnings.push(`${openclawHome} not found. OpenClaw integration remains optional.`);
116
- if (payload.rules.count === 0)
246
+ if (payload.rules.count === 0) {
117
247
  warnings.push('No rule files found in rules directory. Seed bundled defaults with `zigrix onboard` or `zigrix configure --section rules`.');
248
+ }
118
249
  if (!payload.pathReach.zigrixInNonLoginPath) {
119
250
  warnings.push('zigrix is not reachable from non-login shell PATH (checked: /usr/local/bin, /usr/bin). ' +
120
251
  'Run `zigrix onboard` or manually: sudo ln -sfn $(which zigrix) /usr/local/bin/zigrix');
@@ -123,27 +254,69 @@ export function gatherDoctor(loaded, paths) {
123
254
  warnings.push('openclaw is not reachable from non-login shell PATH (checked: /usr/local/bin, /usr/bin). ' +
124
255
  'Run `zigrix onboard` or manually: sudo ln -sfn $(which openclaw) /usr/local/bin/openclaw');
125
256
  }
257
+ if (typeof payload.binaries.openclaw === 'string' &&
258
+ payload.binaries.openclaw.length > 0 &&
259
+ !fs.existsSync(payload.binaries.openclaw)) {
260
+ warnings.push(`Configured openclaw.binPath does not exist: ${payload.binaries.openclaw}`);
261
+ }
262
+ warnings.push(...configConsistency.issues.map((issue) => `Config issue: ${issue}`));
263
+ warnings.push(...configConsistency.warnings.map((warning) => `Config warning: ${warning}`));
126
264
  return {
127
265
  ...payload,
128
266
  summary: {
129
- ready: payload.node.ok && payload.writeAccess.baseDir && payload.files.configExists,
267
+ ready: payload.node.ok &&
268
+ payload.writeAccess.baseDir &&
269
+ payload.files.configExists &&
270
+ payload.configConsistency.ok,
130
271
  warnings,
131
272
  },
132
273
  };
133
274
  }
275
+ export function gatherDoctorFailure(error, options) {
276
+ const configPath = options?.configPath ?? resolveCanonicalConfigPath();
277
+ const baseDir = resolveCanonicalConfigHome();
278
+ const openclawHome = detectOpenClawHome();
279
+ const message = error instanceof Error ? error.message : String(error);
280
+ const payload = buildBasePayload({ configPath, baseDir, openclawHome });
281
+ return {
282
+ ...payload,
283
+ configConsistency: {
284
+ ok: false,
285
+ issues: [`Config load failed: ${message}`],
286
+ warnings: [],
287
+ pathConflicts: [],
288
+ missingStandardScales: [...STANDARD_SCALES],
289
+ orchestrator: {
290
+ id: 'unknown',
291
+ presentInRegistry: false,
292
+ enabled: false,
293
+ participantMode: false,
294
+ inParticipants: false,
295
+ excluded: false,
296
+ eligible: false,
297
+ },
298
+ scaleCoverage: {},
299
+ },
300
+ summary: {
301
+ ready: false,
302
+ warnings: [`Config load failed: ${message}`],
303
+ },
304
+ };
305
+ }
134
306
  export function renderDoctorText(payload) {
135
307
  const lines = [
136
308
  'Zigrix Doctor',
137
- `- Node: ${payload.node.version} (${payload.node.ok ? 'ok' : 'too old'})`,
138
- `- Base dir: ${payload.paths.baseDir}`,
139
- `- Config: ${payload.paths.configPath ?? 'missing'}`,
140
- `- Rules dir: ${payload.rules.dir} (${payload.rules.count} files)`,
141
- `- OpenClaw home: ${payload.openclaw.home} (${payload.openclaw.exists ? 'present' : 'missing'})`,
142
- `- Non-login PATH reach (zigrix): ${payload.pathReach.zigrixInNonLoginPath ? `yes (${payload.pathReach.zigrixNonLoginLocation})` : 'no'}`,
143
- `- Non-login PATH reach (openclaw): ${payload.pathReach.openclawInNonLoginPath ? `yes (${payload.pathReach.openclawNonLoginLocation})` : 'no'}`,
144
- `- Ready: ${payload.summary.ready ? 'yes' : 'no'}`,
309
+ `- Node: ${payload.node?.version ?? 'unknown'} (${payload.node?.ok ? 'ok' : 'too old'})`,
310
+ `- Base dir: ${payload.paths?.baseDir ?? 'missing'}`,
311
+ `- Config: ${payload.paths?.configPath ?? 'missing'}`,
312
+ `- Rules dir: ${payload.rules?.dir ?? 'missing'} (${payload.rules?.count ?? 0} files)`,
313
+ `- OpenClaw home: ${payload.openclaw?.home ?? 'missing'} (${payload.openclaw?.exists ? 'present' : 'missing'})`,
314
+ `- Non-login PATH reach (zigrix): ${payload.pathReach?.zigrixInNonLoginPath ? `yes (${payload.pathReach?.zigrixNonLoginLocation})` : 'no'}`,
315
+ `- Non-login PATH reach (openclaw): ${payload.pathReach?.openclawInNonLoginPath ? `yes (${payload.pathReach?.openclawNonLoginLocation})` : 'no'}`,
316
+ `- Config consistency: ${payload.configConsistency?.ok ? 'ok' : 'issues found'}`,
317
+ `- Ready: ${payload.summary?.ready ? 'yes' : 'no'}`,
145
318
  ];
146
- for (const warning of payload.summary.warnings) {
319
+ for (const warning of (payload.summary?.warnings ?? [])) {
147
320
  lines.push(`- Warning: ${warning}`);
148
321
  }
149
322
  return lines.join('\n');