zigrix 0.1.0-alpha.7 → 0.1.0-alpha.9
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.
- package/LICENSE +1 -1
- package/README.md +248 -112
- package/dist/agents/registry.js +5 -2
- package/dist/agents/roles.d.ts +10 -0
- package/dist/agents/roles.js +83 -0
- package/dist/config/defaults.d.ts +2 -1
- package/dist/config/defaults.js +2 -1
- package/dist/config/schema.d.ts +25 -3
- package/dist/config/schema.js +34 -2
- package/dist/configure.d.ts +1 -0
- package/dist/configure.js +14 -1
- package/dist/dashboard/.next/BUILD_ID +1 -1
- package/dist/dashboard/.next/app-build-manifest.json +10 -10
- package/dist/dashboard/.next/app-path-routes-manifest.json +4 -4
- package/dist/dashboard/.next/build-manifest.json +2 -2
- package/dist/dashboard/.next/server/app/_not-found.html +1 -1
- package/dist/dashboard/.next/server/app/_not-found.rsc +1 -1
- package/dist/dashboard/.next/server/app/api/auth/login/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/logout/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/session/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/auth/setup/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/overview/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/stream/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/cancel/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/conversation/route.js +1 -1
- package/dist/dashboard/.next/server/app/api/tasks/[taskId]/route.js +1 -1
- package/dist/dashboard/.next/server/app/login.html +1 -1
- package/dist/dashboard/.next/server/app/login.rsc +1 -1
- package/dist/dashboard/.next/server/app/setup.html +1 -1
- package/dist/dashboard/.next/server/app/setup.rsc +1 -1
- package/dist/dashboard/.next/server/app-paths-manifest.json +4 -4
- package/dist/dashboard/.next/server/functions-config-manifest.json +3 -3
- package/dist/dashboard/.next/server/middleware.js +1 -1
- package/dist/dashboard/.next/server/pages/404.html +1 -1
- package/dist/dashboard/.next/server/pages/500.html +1 -1
- package/dist/index.js +5 -1
- package/dist/onboard.d.ts +16 -2
- package/dist/onboard.js +128 -9
- package/dist/orchestration/dispatch.d.ts +2 -1
- package/dist/orchestration/dispatch.js +157 -41
- package/dist/orchestration/evidence.js +17 -3
- package/dist/orchestration/finalize.js +2 -2
- package/dist/orchestration/report.js +6 -1
- package/dist/orchestration/worker.d.ts +1 -1
- package/dist/orchestration/worker.js +17 -2
- package/dist/rules/templates.js +3 -6
- package/dist/state/tasks.d.ts +7 -0
- package/package.json +1 -1
- package/skills/zigrix-main-agent-guide/SKILL.md +118 -0
- /package/dist/dashboard/.next/static/{afoa9JVywKLyR6X4Cxspl → TlUj0t8APzTccK13DVZZW}/_buildManifest.js +0 -0
- /package/dist/dashboard/.next/static/{afoa9JVywKLyR6X4Cxspl → TlUj0t8APzTccK13DVZZW}/_ssgManifest.js +0 -0
package/dist/onboard.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type StandardAgentRole } from './agents/roles.js';
|
|
1
2
|
import type { ZigrixConfig } from './config/schema.js';
|
|
2
3
|
import { type ZigrixPaths } from './state/paths.js';
|
|
3
4
|
export interface OpenClawAgent {
|
|
@@ -14,6 +15,7 @@ export interface OpenClawConfig {
|
|
|
14
15
|
list?: OpenClawAgent[];
|
|
15
16
|
};
|
|
16
17
|
}
|
|
18
|
+
export type AgentRoleAssignments = Record<string, StandardAgentRole>;
|
|
17
19
|
export interface PathStabilizeResult {
|
|
18
20
|
alreadyInPath: boolean;
|
|
19
21
|
symlinkCreated: boolean;
|
|
@@ -50,12 +52,13 @@ export interface OnboardResult {
|
|
|
50
52
|
export interface RunOnboardOptions {
|
|
51
53
|
yes?: boolean;
|
|
52
54
|
projectDir?: string;
|
|
55
|
+
orchestratorId?: string;
|
|
53
56
|
silent?: boolean;
|
|
54
57
|
}
|
|
55
58
|
export declare function detectOpenClawHome(): string;
|
|
56
59
|
export declare function loadOpenClawConfig(openclawHome: string): OpenClawConfig | null;
|
|
57
60
|
export declare function filterAgents(agents: OpenClawAgent[]): OpenClawAgent[];
|
|
58
|
-
export declare function registerAgents(config: ZigrixConfig, agents: OpenClawAgent[]): {
|
|
61
|
+
export declare function registerAgents(config: ZigrixConfig, agents: OpenClawAgent[], roleAssignments?: AgentRoleAssignments): {
|
|
59
62
|
config: ZigrixConfig;
|
|
60
63
|
registered: string[];
|
|
61
64
|
skipped: string[];
|
|
@@ -69,7 +72,10 @@ export declare function seedRules(sourceDir: string, targetDir: string): {
|
|
|
69
72
|
skipped: string[];
|
|
70
73
|
source: string;
|
|
71
74
|
};
|
|
72
|
-
export declare
|
|
75
|
+
export declare const STABLE_SHELL_PATHS: string[];
|
|
76
|
+
export declare function checkZigrixInPath(opts?: {
|
|
77
|
+
_overrideStablePaths?: string[];
|
|
78
|
+
}): boolean;
|
|
73
79
|
/**
|
|
74
80
|
* Resolve the path to the zigrix CLI entry point (dist/index.js).
|
|
75
81
|
* Works whether installed via npm link, npm install -g, or local checkout.
|
|
@@ -94,9 +100,11 @@ export declare function findUserBinDir(): string;
|
|
|
94
100
|
* 2. ~/.local/bin — user-local fallback; may not be in PATH, shows warning if so
|
|
95
101
|
*
|
|
96
102
|
* @param opts._overrideSystemBinDir - Override system bin dir selection (for testing)
|
|
103
|
+
* @param opts._overrideStablePaths - Override stable paths list (for testing)
|
|
97
104
|
*/
|
|
98
105
|
export declare function ensureZigrixInPath(opts?: {
|
|
99
106
|
_overrideSystemBinDir?: string | null;
|
|
107
|
+
_overrideStablePaths?: string[];
|
|
100
108
|
}): PathStabilizeResult;
|
|
101
109
|
/**
|
|
102
110
|
* Find the skills/ directory bundled with this zigrix package.
|
|
@@ -113,4 +121,10 @@ export declare function registerSkills(openclawHome: string): SkillRegistrationR
|
|
|
113
121
|
* Space to toggle, Enter to confirm. All agents pre-selected by default.
|
|
114
122
|
*/
|
|
115
123
|
export declare function promptAgentSelection(agents: OpenClawAgent[]): Promise<OpenClawAgent[]>;
|
|
124
|
+
export declare function promptAgentRoleAssignments(agents: OpenClawAgent[]): Promise<AgentRoleAssignments>;
|
|
125
|
+
export declare function ensureOrchestratorId(config: ZigrixConfig, preferredId?: string): {
|
|
126
|
+
config: ZigrixConfig;
|
|
127
|
+
changed: boolean;
|
|
128
|
+
warning?: string;
|
|
129
|
+
};
|
|
116
130
|
export declare function runOnboard(options: RunOnboardOptions): Promise<OnboardResult>;
|
package/dist/onboard.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { addAgent } from './agents/registry.js';
|
|
5
|
+
import { inferStandardAgentRole, STANDARD_AGENT_ROLES } from './agents/roles.js';
|
|
5
6
|
import { loadConfig, writeConfigFile, writeDefaultConfig } from './config/load.js';
|
|
6
7
|
import { ensureBaseState, resolvePaths } from './state/paths.js';
|
|
7
8
|
import { rebuildIndex } from './state/tasks.js';
|
|
@@ -28,7 +29,7 @@ export function filterAgents(agents) {
|
|
|
28
29
|
return agents.filter((a) => a.id !== 'main');
|
|
29
30
|
}
|
|
30
31
|
// ─── Agent registration ───────────────────────────────────────────────────────
|
|
31
|
-
export function registerAgents(config, agents) {
|
|
32
|
+
export function registerAgents(config, agents, roleAssignments) {
|
|
32
33
|
let current = structuredClone(config);
|
|
33
34
|
const registered = [];
|
|
34
35
|
const skipped = [];
|
|
@@ -38,7 +39,10 @@ export function registerAgents(config, agents) {
|
|
|
38
39
|
skipped.push(agent.id);
|
|
39
40
|
continue;
|
|
40
41
|
}
|
|
41
|
-
const role = agent.
|
|
42
|
+
const role = roleAssignments?.[agent.id] ?? inferStandardAgentRole({
|
|
43
|
+
agentId: agent.id,
|
|
44
|
+
theme: agent.identity?.theme ?? null,
|
|
45
|
+
});
|
|
42
46
|
const result = addAgent(current, {
|
|
43
47
|
id: agent.id,
|
|
44
48
|
role,
|
|
@@ -102,9 +106,9 @@ export function seedRules(sourceDir, targetDir) {
|
|
|
102
106
|
return { copied, skipped, source: effectiveSource === sourceDir ? 'external' : 'bundled' };
|
|
103
107
|
}
|
|
104
108
|
// ─── PATH check and stabilization ─────────────────────────────────────────────
|
|
105
|
-
export
|
|
106
|
-
|
|
107
|
-
const dirs =
|
|
109
|
+
export const STABLE_SHELL_PATHS = ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin'];
|
|
110
|
+
export function checkZigrixInPath(opts) {
|
|
111
|
+
const dirs = opts?._overrideStablePaths ?? STABLE_SHELL_PATHS;
|
|
108
112
|
for (const dir of dirs) {
|
|
109
113
|
try {
|
|
110
114
|
if (!fs.existsSync(dir))
|
|
@@ -215,9 +219,10 @@ export function findUserBinDir() {
|
|
|
215
219
|
* 2. ~/.local/bin — user-local fallback; may not be in PATH, shows warning if so
|
|
216
220
|
*
|
|
217
221
|
* @param opts._overrideSystemBinDir - Override system bin dir selection (for testing)
|
|
222
|
+
* @param opts._overrideStablePaths - Override stable paths list (for testing)
|
|
218
223
|
*/
|
|
219
224
|
export function ensureZigrixInPath(opts) {
|
|
220
|
-
if (checkZigrixInPath()) {
|
|
225
|
+
if (checkZigrixInPath({ _overrideStablePaths: opts?._overrideStablePaths })) {
|
|
221
226
|
return { alreadyInPath: true, symlinkCreated: false, symlinkPath: null, warning: null };
|
|
222
227
|
}
|
|
223
228
|
const binEntry = resolveZigrixBin();
|
|
@@ -249,7 +254,15 @@ export function ensureZigrixInPath(opts) {
|
|
|
249
254
|
fs.chmodSync(symlinkPath, 0o755);
|
|
250
255
|
return { alreadyInPath: false, symlinkCreated: true, symlinkPath, warning: null };
|
|
251
256
|
}
|
|
252
|
-
catch {
|
|
257
|
+
catch (e) {
|
|
258
|
+
if (e.code === 'EACCES') {
|
|
259
|
+
return {
|
|
260
|
+
alreadyInPath: false,
|
|
261
|
+
symlinkCreated: false,
|
|
262
|
+
symlinkPath: null,
|
|
263
|
+
warning: `Cannot write to ${systemBinDir} (permission denied). Run:\n sudo ln -sfn $(which zigrix) ${symlinkPath}`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
253
266
|
// Fall through to user bin dir
|
|
254
267
|
}
|
|
255
268
|
}
|
|
@@ -418,6 +431,85 @@ export async function promptAgentSelection(agents) {
|
|
|
418
431
|
return agents;
|
|
419
432
|
}
|
|
420
433
|
}
|
|
434
|
+
export async function promptAgentRoleAssignments(agents) {
|
|
435
|
+
const defaults = Object.fromEntries(agents.map((agent) => [agent.id, inferStandardAgentRole({ agentId: agent.id, theme: agent.identity?.theme ?? null })]));
|
|
436
|
+
if (agents.length === 0)
|
|
437
|
+
return defaults;
|
|
438
|
+
try {
|
|
439
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
440
|
+
const assignments = {};
|
|
441
|
+
for (const agent of agents) {
|
|
442
|
+
const suggested = defaults[agent.id];
|
|
443
|
+
let selected = [];
|
|
444
|
+
while (selected.length !== 1) {
|
|
445
|
+
selected = await checkbox({
|
|
446
|
+
message: `Assign role for ${agent.id} (space toggle + enter confirm, choose exactly one):`,
|
|
447
|
+
choices: STANDARD_AGENT_ROLES.map((role) => ({
|
|
448
|
+
name: `${role}${role === suggested ? ' (suggested)' : ''}`,
|
|
449
|
+
value: role,
|
|
450
|
+
checked: role === suggested,
|
|
451
|
+
})),
|
|
452
|
+
});
|
|
453
|
+
if (selected.length !== 1) {
|
|
454
|
+
console.log(`⚠️ Select exactly one role for ${agent.id}.`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
assignments[agent.id] = selected[0];
|
|
458
|
+
}
|
|
459
|
+
return assignments;
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
console.log('ℹ️ Non-interactive mode — inferring roles from agent ids/themes.');
|
|
463
|
+
return defaults;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
export function ensureOrchestratorId(config, preferredId) {
|
|
467
|
+
const next = structuredClone(config);
|
|
468
|
+
const participants = new Set(next.agents.orchestration.participants);
|
|
469
|
+
const excluded = new Set(next.agents.orchestration.excluded);
|
|
470
|
+
const participantMode = participants.size > 0;
|
|
471
|
+
const eligible = Object.entries(next.agents.registry)
|
|
472
|
+
.filter(([agentId, agent]) => {
|
|
473
|
+
if (!agent.enabled)
|
|
474
|
+
return false;
|
|
475
|
+
if (agent.role !== 'orchestrator')
|
|
476
|
+
return false;
|
|
477
|
+
if (excluded.has(agentId))
|
|
478
|
+
return false;
|
|
479
|
+
if (participantMode && !participants.has(agentId))
|
|
480
|
+
return false;
|
|
481
|
+
return true;
|
|
482
|
+
})
|
|
483
|
+
.map(([agentId]) => agentId)
|
|
484
|
+
.sort();
|
|
485
|
+
const requested = preferredId?.trim();
|
|
486
|
+
const current = next.agents.orchestration.orchestratorId;
|
|
487
|
+
if (requested) {
|
|
488
|
+
if (!eligible.includes(requested)) {
|
|
489
|
+
return {
|
|
490
|
+
config: next,
|
|
491
|
+
changed: false,
|
|
492
|
+
warning: `requested orchestrator '${requested}' is not eligible (eligible: ${eligible.join(', ') || 'none'})`,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
const changed = current !== requested;
|
|
496
|
+
next.agents.orchestration.orchestratorId = requested;
|
|
497
|
+
return { config: next, changed };
|
|
498
|
+
}
|
|
499
|
+
if (eligible.includes(current)) {
|
|
500
|
+
return { config: next, changed: false };
|
|
501
|
+
}
|
|
502
|
+
const fallback = eligible[0];
|
|
503
|
+
if (!fallback) {
|
|
504
|
+
return {
|
|
505
|
+
config: next,
|
|
506
|
+
changed: false,
|
|
507
|
+
warning: 'no eligible orchestrator role agent found. set one with --orchestrator-id after assigning roles.',
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
next.agents.orchestration.orchestratorId = fallback;
|
|
511
|
+
return { config: next, changed: true };
|
|
512
|
+
}
|
|
421
513
|
// ─── Ensure config (idempotent) ───────────────────────────────────────────────
|
|
422
514
|
function ensureConfig() {
|
|
423
515
|
const existing = loadConfig({});
|
|
@@ -471,18 +563,45 @@ export async function runOnboard(options) {
|
|
|
471
563
|
else {
|
|
472
564
|
selectedAgents = await promptAgentSelection(allAgents);
|
|
473
565
|
}
|
|
566
|
+
let nextConfig = loaded.config;
|
|
474
567
|
if (selectedAgents.length > 0) {
|
|
475
|
-
const
|
|
568
|
+
const roleAssignments = options.yes
|
|
569
|
+
? Object.fromEntries(selectedAgents.map((agent) => [agent.id, inferStandardAgentRole({ agentId: agent.id, theme: agent.identity?.theme ?? null })]))
|
|
570
|
+
: await promptAgentRoleAssignments(selectedAgents);
|
|
571
|
+
const result = registerAgents(nextConfig, selectedAgents, roleAssignments);
|
|
572
|
+
nextConfig = result.config;
|
|
476
573
|
agentsRegistered = result.registered;
|
|
477
574
|
agentsSkipped = result.skipped;
|
|
478
575
|
if (agentsRegistered.length > 0) {
|
|
479
|
-
writeConfigFile(configPath, result.config);
|
|
480
576
|
log(`✅ Registered agents: ${agentsRegistered.join(', ')}`);
|
|
481
577
|
}
|
|
482
578
|
if (agentsSkipped.length > 0) {
|
|
483
579
|
log(`⏭️ Already registered (skipped): ${agentsSkipped.join(', ')}`);
|
|
484
580
|
}
|
|
485
581
|
}
|
|
582
|
+
const orchResult = ensureOrchestratorId(nextConfig, options.orchestratorId);
|
|
583
|
+
nextConfig = orchResult.config;
|
|
584
|
+
if (orchResult.warning) {
|
|
585
|
+
warnings.push(orchResult.warning);
|
|
586
|
+
log(`⚠️ ${orchResult.warning}`);
|
|
587
|
+
}
|
|
588
|
+
if (agentsRegistered.length > 0 || orchResult.changed) {
|
|
589
|
+
writeConfigFile(configPath, nextConfig);
|
|
590
|
+
if (orchResult.changed) {
|
|
591
|
+
log(`✅ Orchestrator set to: ${nextConfig.agents.orchestration.orchestratorId}`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (!openclawConfig && options.orchestratorId) {
|
|
596
|
+
const orchResult = ensureOrchestratorId(loaded.config, options.orchestratorId);
|
|
597
|
+
if (orchResult.warning) {
|
|
598
|
+
warnings.push(orchResult.warning);
|
|
599
|
+
log(`⚠️ ${orchResult.warning}`);
|
|
600
|
+
}
|
|
601
|
+
if (orchResult.changed) {
|
|
602
|
+
writeConfigFile(configPath, orchResult.config);
|
|
603
|
+
log(`✅ Orchestrator set to: ${orchResult.config.agents.orchestration.orchestratorId}`);
|
|
604
|
+
}
|
|
486
605
|
}
|
|
487
606
|
// 4. Seed rules
|
|
488
607
|
const projectDir = options.projectDir ?? process.cwd();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { ZigrixConfig } from '../config/schema.js';
|
|
1
2
|
import { type ZigrixPaths } from '../state/paths.js';
|
|
2
|
-
export declare function dispatchTask(paths: ZigrixPaths, params: {
|
|
3
|
+
export declare function dispatchTask(paths: ZigrixPaths, config: ZigrixConfig, params: {
|
|
3
4
|
title: string;
|
|
4
5
|
description: string;
|
|
5
6
|
scale: string;
|
|
@@ -1,18 +1,106 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { ROLE_HINTS } from '../agents/roles.js';
|
|
3
4
|
import { appendEvent } from '../state/events.js';
|
|
4
|
-
import { ensureBaseState } from '../state/paths.js';
|
|
5
5
|
import { createTask, rebuildIndex, saveTask } from '../state/tasks.js';
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
import { ensureBaseState } from '../state/paths.js';
|
|
7
|
+
const BASELINE_REQUIRED_ROLES = ['orchestrator', 'qa'];
|
|
8
|
+
const CANDIDATE_ROLE_ORDER = ['frontend', 'backend', 'system', 'security'];
|
|
9
|
+
function unique(items) {
|
|
10
|
+
return [...new Set(items)];
|
|
11
|
+
}
|
|
12
|
+
function listEligibleAgentsByRole(config) {
|
|
13
|
+
const byRole = new Map();
|
|
14
|
+
const participants = new Set(config.agents.orchestration.participants);
|
|
15
|
+
const excluded = new Set(config.agents.orchestration.excluded);
|
|
16
|
+
const participantMode = participants.size > 0;
|
|
17
|
+
for (const [agentId, agent] of Object.entries(config.agents.registry).sort(([a], [b]) => a.localeCompare(b))) {
|
|
18
|
+
if (!agent.enabled)
|
|
19
|
+
continue;
|
|
20
|
+
if (excluded.has(agentId))
|
|
21
|
+
continue;
|
|
22
|
+
if (participantMode && !participants.has(agentId))
|
|
23
|
+
continue;
|
|
24
|
+
const role = agent.role;
|
|
25
|
+
const row = byRole.get(role) ?? [];
|
|
26
|
+
row.push(agentId);
|
|
27
|
+
byRole.set(role, row);
|
|
28
|
+
}
|
|
29
|
+
return byRole;
|
|
30
|
+
}
|
|
31
|
+
function pickRequiredAgentByRole(params) {
|
|
32
|
+
if (params.role === 'orchestrator') {
|
|
33
|
+
if (params.roleAgents.length === 0) {
|
|
34
|
+
throw new Error('dispatch validation failed: no eligible agent for required role "orchestrator"');
|
|
35
|
+
}
|
|
36
|
+
if (!params.roleAgents.includes(params.orchestratorId)) {
|
|
37
|
+
throw new Error(`dispatch validation failed: configured orchestratorId '${params.orchestratorId}' is not eligible (available: ${params.roleAgents.join(', ')})`);
|
|
38
|
+
}
|
|
39
|
+
return params.orchestratorId;
|
|
40
|
+
}
|
|
41
|
+
const picked = params.roleAgents[0];
|
|
42
|
+
if (!picked) {
|
|
43
|
+
throw new Error(`dispatch validation failed: no eligible agent for required role "${params.role}"`);
|
|
44
|
+
}
|
|
45
|
+
return picked;
|
|
46
|
+
}
|
|
47
|
+
function resolveAgentSelection(config, scale) {
|
|
48
|
+
const scalePolicy = config.rules.scales[scale];
|
|
49
|
+
if (!scalePolicy) {
|
|
50
|
+
throw new Error(`unknown scale: ${scale}`);
|
|
51
|
+
}
|
|
52
|
+
const requiredRoles = unique([...BASELINE_REQUIRED_ROLES, ...scalePolicy.requiredRoles]);
|
|
53
|
+
const optionalRoles = unique(scalePolicy.optionalRoles.filter((role) => !requiredRoles.includes(role)));
|
|
54
|
+
const eligibleByRole = listEligibleAgentsByRole(config);
|
|
55
|
+
const requiredAgents = [];
|
|
56
|
+
const roleAgentMap = {};
|
|
57
|
+
for (const role of requiredRoles) {
|
|
58
|
+
const roleAgents = eligibleByRole.get(role) ?? [];
|
|
59
|
+
roleAgentMap[role] = [...roleAgents];
|
|
60
|
+
const picked = pickRequiredAgentByRole({
|
|
61
|
+
role,
|
|
62
|
+
roleAgents,
|
|
63
|
+
orchestratorId: config.agents.orchestration.orchestratorId,
|
|
64
|
+
});
|
|
65
|
+
requiredAgents.push(picked);
|
|
66
|
+
}
|
|
67
|
+
const candidateRoles = unique([...optionalRoles, ...CANDIDATE_ROLE_ORDER.filter((role) => !requiredRoles.includes(role))]);
|
|
68
|
+
const candidateAgents = [];
|
|
69
|
+
for (const role of candidateRoles) {
|
|
70
|
+
const roleAgents = eligibleByRole.get(role) ?? [];
|
|
71
|
+
roleAgentMap[role] = [...roleAgents];
|
|
72
|
+
for (const agentId of roleAgents) {
|
|
73
|
+
if (requiredAgents.includes(agentId))
|
|
74
|
+
continue;
|
|
75
|
+
if (!candidateAgents.includes(agentId))
|
|
76
|
+
candidateAgents.push(agentId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const selectionHints = {};
|
|
80
|
+
for (const [role, agentIds] of Object.entries(roleAgentMap)) {
|
|
81
|
+
const hint = ROLE_HINTS[role] ?? 'role-based selection';
|
|
82
|
+
for (const agentId of agentIds) {
|
|
83
|
+
selectionHints[agentId] = `${hint} (role: ${role})`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const qaAgentId = requiredAgents.find((agentId) => {
|
|
87
|
+
const role = config.agents.registry[agentId]?.role;
|
|
88
|
+
return role === 'qa';
|
|
89
|
+
});
|
|
90
|
+
if (!qaAgentId) {
|
|
91
|
+
throw new Error('dispatch validation failed: no selected QA agent');
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
requiredRoles,
|
|
95
|
+
optionalRoles,
|
|
96
|
+
requiredAgents,
|
|
97
|
+
candidateAgents,
|
|
98
|
+
roleAgentMap,
|
|
99
|
+
selectionHints,
|
|
100
|
+
orchestratorId: config.agents.orchestration.orchestratorId,
|
|
101
|
+
qaAgentId,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
16
104
|
function defaultWorkPackages(scale) {
|
|
17
105
|
return [
|
|
18
106
|
{ id: 'WP1', key: 'planning', title: 'planning', parallel: false },
|
|
@@ -21,33 +109,33 @@ function defaultWorkPackages(scale) {
|
|
|
21
109
|
{ id: 'WP4', key: 'release', title: 'release', parallel: false },
|
|
22
110
|
];
|
|
23
111
|
}
|
|
24
|
-
function defaultExecutionUnits(scale) {
|
|
112
|
+
function defaultExecutionUnits(scale, owners) {
|
|
25
113
|
if (['normal', 'risky', 'large'].includes(scale)) {
|
|
26
114
|
return [
|
|
27
|
-
{ id: 'U1', title: 'spec confirmation', kind: 'planning', owner:
|
|
28
|
-
{ id: 'U2', title: 'implementation planning / work package split', kind: 'planning', owner:
|
|
29
|
-
{ id: 'U3', title: 'implementation slices', kind: 'implementation', owner:
|
|
30
|
-
{ id: 'U4', title: 'qa / regression', kind: 'verification', owner:
|
|
31
|
-
{ id: 'U5', title: 'report / deploy / wrap-up', kind: 'reporting', owner:
|
|
115
|
+
{ id: 'U1', title: 'spec confirmation', kind: 'planning', owner: owners.orchestratorId, workPackage: 'planning', dependsOn: [], parallel: false, status: 'OPEN', dod: 'scope / constraints / edge cases fixed' },
|
|
116
|
+
{ id: 'U2', title: 'implementation planning / work package split', kind: 'planning', owner: owners.orchestratorId, workPackage: 'planning', dependsOn: ['U1'], parallel: false, status: 'OPEN', dod: 'execution units and work packages fixed' },
|
|
117
|
+
{ id: 'U3', title: 'implementation slices', kind: 'implementation', owner: owners.orchestratorId, workPackage: 'implementation', dependsOn: ['U2'], parallel: true, status: 'OPEN', dod: 'required work packages complete' },
|
|
118
|
+
{ id: 'U4', title: 'qa / regression', kind: 'verification', owner: owners.qaAgentId, workPackage: 'verification', dependsOn: ['U3'], parallel: false, status: 'OPEN', dod: 'qa evidence attached' },
|
|
119
|
+
{ id: 'U5', title: 'report / deploy / wrap-up', kind: 'reporting', owner: owners.orchestratorId, workPackage: 'release', dependsOn: ['U4'], parallel: false, status: 'OPEN', dod: 'final report prepared and deployment decision recorded' },
|
|
32
120
|
];
|
|
33
121
|
}
|
|
34
122
|
return [
|
|
35
|
-
{ id: 'U1', title: 'spec confirmation', kind: 'planning', owner:
|
|
36
|
-
{ id: 'U2', title: 'implementation slice', kind: 'implementation', owner:
|
|
37
|
-
{ id: 'U3', title: 'qa / regression', kind: 'verification', owner:
|
|
123
|
+
{ id: 'U1', title: 'spec confirmation', kind: 'planning', owner: owners.orchestratorId, workPackage: 'planning', dependsOn: [], parallel: false, status: 'OPEN', dod: 'scope / constraints / edge cases fixed' },
|
|
124
|
+
{ id: 'U2', title: 'implementation slice', kind: 'implementation', owner: owners.orchestratorId, workPackage: 'implementation', dependsOn: ['U1'], parallel: false, status: 'OPEN', dod: 'main implementation slice complete' },
|
|
125
|
+
{ id: 'U3', title: 'qa / regression', kind: 'verification', owner: owners.qaAgentId, workPackage: 'verification', dependsOn: ['U2'], parallel: false, status: 'OPEN', dod: 'qa evidence attached' },
|
|
38
126
|
];
|
|
39
127
|
}
|
|
40
|
-
|
|
41
|
-
function buildBootPrompt(task) {
|
|
128
|
+
function buildBootPrompt(task, options) {
|
|
42
129
|
return `## Orchestration Task Boot: ${task.taskId}
|
|
43
130
|
- **Title:** ${task.title}
|
|
44
131
|
- **Scale:** ${task.scale}
|
|
132
|
+
- **Orchestrator:** ${options.orchestratorId}
|
|
45
133
|
|
|
46
134
|
---
|
|
47
135
|
|
|
48
|
-
## ⚠️ 절대 규칙:
|
|
136
|
+
## ⚠️ 절대 규칙: QA 역할 워커 호출 필수
|
|
49
137
|
|
|
50
|
-
|
|
138
|
+
**이 태스크는 QA 역할(${options.qaAgentId}) 워커 완료가 필수다.**
|
|
51
139
|
|
|
52
140
|
---
|
|
53
141
|
|
|
@@ -78,24 +166,31 @@ zigrix task finalize ${task.taskId} --json
|
|
|
78
166
|
\`\`\`
|
|
79
167
|
`;
|
|
80
168
|
}
|
|
81
|
-
|
|
82
|
-
export function dispatchTask(paths, params) {
|
|
169
|
+
export function dispatchTask(paths, config, params) {
|
|
83
170
|
ensureBaseState(paths);
|
|
84
|
-
|
|
171
|
+
const selection = resolveAgentSelection(config, params.scale);
|
|
85
172
|
const task = createTask(paths, {
|
|
86
173
|
title: params.title,
|
|
87
174
|
description: params.description,
|
|
88
175
|
scale: params.scale,
|
|
89
|
-
requiredAgents: [...
|
|
176
|
+
requiredAgents: [...selection.requiredAgents],
|
|
90
177
|
projectDir: params.projectDir,
|
|
91
178
|
requestedBy: params.requestedBy,
|
|
92
179
|
});
|
|
93
|
-
|
|
94
|
-
task.selectedAgents = [...BASELINE_REQUIRED];
|
|
180
|
+
task.selectedAgents = [...selection.requiredAgents];
|
|
95
181
|
task.workPackages = defaultWorkPackages(params.scale);
|
|
96
|
-
task.executionUnits = defaultExecutionUnits(params.scale
|
|
182
|
+
task.executionUnits = defaultExecutionUnits(params.scale, {
|
|
183
|
+
orchestratorId: selection.orchestratorId,
|
|
184
|
+
qaAgentId: selection.qaAgentId,
|
|
185
|
+
});
|
|
186
|
+
task.baselineRequiredAgents = [...selection.requiredAgents];
|
|
187
|
+
task.candidateAgents = [...selection.candidateAgents];
|
|
188
|
+
task.requiredRoles = [...selection.requiredRoles];
|
|
189
|
+
task.optionalRoles = [...selection.optionalRoles];
|
|
190
|
+
task.roleAgentMap = selection.roleAgentMap;
|
|
191
|
+
task.orchestratorId = selection.orchestratorId;
|
|
192
|
+
task.qaAgentId = selection.qaAgentId;
|
|
97
193
|
saveTask(paths, task);
|
|
98
|
-
// Write dispatch prompt
|
|
99
194
|
const promptPath = path.join(paths.promptsDir, `${task.taskId}-dispatch.md`);
|
|
100
195
|
const dispatchPrompt = [
|
|
101
196
|
`## Orchestration Task: ${task.taskId}`,
|
|
@@ -103,19 +198,30 @@ export function dispatchTask(paths, params) {
|
|
|
103
198
|
'### 기본 정보',
|
|
104
199
|
`- **Title:** ${task.title}`,
|
|
105
200
|
`- **Scale:** ${task.scale}`,
|
|
106
|
-
`- **
|
|
107
|
-
`- **
|
|
201
|
+
`- **Orchestrator:** ${selection.orchestratorId}`,
|
|
202
|
+
`- **Baseline Required Agents:** ${selection.requiredAgents.join(', ')}`,
|
|
203
|
+
`- **Candidate Agents:** ${selection.candidateAgents.length > 0 ? selection.candidateAgents.join(', ') : '(none)'}`,
|
|
204
|
+
`- **Required Roles:** ${selection.requiredRoles.join(', ')}`,
|
|
205
|
+
`- **Optional Roles:** ${selection.optionalRoles.length > 0 ? selection.optionalRoles.join(', ') : '(none)'}`,
|
|
108
206
|
params.projectDir ? `- **Project Dir:** ${params.projectDir}` : '',
|
|
109
207
|
'',
|
|
110
208
|
'### 요청 내용',
|
|
111
209
|
params.description,
|
|
112
210
|
params.constraints ? `\n### 제약사항\n${params.constraints}` : '',
|
|
113
211
|
'',
|
|
212
|
+
'### 역할 매핑',
|
|
213
|
+
...Object.entries(selection.roleAgentMap)
|
|
214
|
+
.filter(([, agentIds]) => agentIds.length > 0)
|
|
215
|
+
.map(([role, agentIds]) => `- ${role}: ${agentIds.join(', ')}`),
|
|
216
|
+
'',
|
|
114
217
|
'### 선택 규칙',
|
|
115
|
-
...Object.entries(
|
|
218
|
+
...Object.entries(selection.selectionHints).map(([agentId, hint]) => `- ${agentId}: ${hint}`),
|
|
116
219
|
].filter(Boolean).join('\n');
|
|
117
220
|
fs.writeFileSync(promptPath, `${dispatchPrompt}\n`, 'utf8');
|
|
118
|
-
const
|
|
221
|
+
const orchestratorPrompt = buildBootPrompt(task, {
|
|
222
|
+
orchestratorId: selection.orchestratorId,
|
|
223
|
+
qaAgentId: selection.qaAgentId,
|
|
224
|
+
});
|
|
119
225
|
appendEvent(paths.eventsFile, {
|
|
120
226
|
event: 'task_dispatched',
|
|
121
227
|
taskId: task.taskId,
|
|
@@ -124,8 +230,12 @@ export function dispatchTask(paths, params) {
|
|
|
124
230
|
status: 'OPEN',
|
|
125
231
|
payload: {
|
|
126
232
|
scale: task.scale,
|
|
127
|
-
|
|
128
|
-
|
|
233
|
+
orchestratorId: selection.orchestratorId,
|
|
234
|
+
baselineRequiredAgents: selection.requiredAgents,
|
|
235
|
+
candidateAgents: selection.candidateAgents,
|
|
236
|
+
requiredRoles: selection.requiredRoles,
|
|
237
|
+
optionalRoles: selection.optionalRoles,
|
|
238
|
+
roleAgentMap: selection.roleAgentMap,
|
|
129
239
|
projectDir: params.projectDir ?? null,
|
|
130
240
|
},
|
|
131
241
|
});
|
|
@@ -135,12 +245,18 @@ export function dispatchTask(paths, params) {
|
|
|
135
245
|
taskId: task.taskId,
|
|
136
246
|
title: task.title,
|
|
137
247
|
scale: task.scale,
|
|
138
|
-
|
|
139
|
-
|
|
248
|
+
orchestratorId: selection.orchestratorId,
|
|
249
|
+
qaAgentId: selection.qaAgentId,
|
|
250
|
+
baselineRequiredAgents: selection.requiredAgents,
|
|
251
|
+
candidateAgents: selection.candidateAgents,
|
|
252
|
+
requiredRoles: selection.requiredRoles,
|
|
253
|
+
optionalRoles: selection.optionalRoles,
|
|
254
|
+
roleAgentMap: selection.roleAgentMap,
|
|
140
255
|
specPath: path.join(paths.tasksDir, `${task.taskId}.md`),
|
|
141
256
|
metaPath: path.join(paths.tasksDir, `${task.taskId}.meta.json`),
|
|
142
257
|
promptPath,
|
|
143
|
-
|
|
258
|
+
orchestratorPrompt,
|
|
259
|
+
proZigPrompt: orchestratorPrompt,
|
|
144
260
|
projectDir: params.projectDir ?? null,
|
|
145
261
|
};
|
|
146
262
|
}
|
|
@@ -32,6 +32,19 @@ function extractEvidence(rows) {
|
|
|
32
32
|
}
|
|
33
33
|
return { lastAssistant, toolResults: toolResults.slice(-3) };
|
|
34
34
|
}
|
|
35
|
+
function resolveQaAgentId(task) {
|
|
36
|
+
if (typeof task.qaAgentId === 'string' && task.qaAgentId.trim().length > 0) {
|
|
37
|
+
return task.qaAgentId;
|
|
38
|
+
}
|
|
39
|
+
const roleMap = task.roleAgentMap;
|
|
40
|
+
if (roleMap && typeof roleMap === 'object') {
|
|
41
|
+
const qaAgents = roleMap.qa;
|
|
42
|
+
if (Array.isArray(qaAgents) && qaAgents.length > 0) {
|
|
43
|
+
return String(qaAgents[0]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return 'qa-zig';
|
|
47
|
+
}
|
|
35
48
|
export function collectEvidence(paths, params) {
|
|
36
49
|
ensureBaseState(paths);
|
|
37
50
|
const task = loadTask(paths, params.taskId);
|
|
@@ -83,9 +96,10 @@ export function mergeEvidence(paths, params) {
|
|
|
83
96
|
const presentAgents = [...new Set(items.map((item) => String(item.agentId)))].sort();
|
|
84
97
|
const requiredAgents = [...(params.requiredAgents?.length ? params.requiredAgents : resolveRequiredAgents(task))];
|
|
85
98
|
const missingAgents = requiredAgents.filter((agentId) => !presentAgents.includes(agentId));
|
|
86
|
-
const
|
|
99
|
+
const qaAgentId = resolveQaAgentId(task);
|
|
100
|
+
const qaPresent = presentAgents.includes(qaAgentId);
|
|
87
101
|
const complete = missingAgents.length === 0 && (!(params.requireQa ?? false) || qaPresent);
|
|
88
|
-
const merged = { ts: nowIso(), taskId: params.taskId, requiredAgents, presentAgents, missingAgents, qaPresent, complete, items };
|
|
102
|
+
const merged = { ts: nowIso(), taskId: params.taskId, requiredAgents, presentAgents, missingAgents, qaAgentId, qaPresent, complete, items };
|
|
89
103
|
const outPath = path.join(taskDir, '_merged.json');
|
|
90
104
|
fs.writeFileSync(outPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
|
|
91
105
|
appendEvent(paths.eventsFile, {
|
|
@@ -93,5 +107,5 @@ export function mergeEvidence(paths, params) {
|
|
|
93
107
|
payload: { requiredAgents, missingAgents, complete, mergedPath: outPath, qaPresent },
|
|
94
108
|
});
|
|
95
109
|
rebuildIndex(paths);
|
|
96
|
-
return { ok: true, taskId: params.taskId, complete, missingAgents, mergedPath: outPath };
|
|
110
|
+
return { ok: true, taskId: params.taskId, complete, missingAgents, qaAgentId, qaPresent, mergedPath: outPath };
|
|
97
111
|
}
|
|
@@ -15,8 +15,8 @@ function autoCloseCompletedUnits(task) {
|
|
|
15
15
|
if (s.status === 'done')
|
|
16
16
|
doneAgents.add(agentId);
|
|
17
17
|
}
|
|
18
|
-
//
|
|
19
|
-
doneAgents.add('pro-zig');
|
|
18
|
+
// orchestrator is always "done" at finalize time
|
|
19
|
+
doneAgents.add(task.orchestratorId ?? 'pro-zig');
|
|
20
20
|
let changed = false;
|
|
21
21
|
for (const unit of units) {
|
|
22
22
|
if (['OPEN', 'IN_PROGRESS'].includes(unit.status.toUpperCase()) && doneAgents.has(unit.owner)) {
|
|
@@ -44,7 +44,12 @@ function collectRisks(merged) {
|
|
|
44
44
|
}
|
|
45
45
|
function qaLine(merged) {
|
|
46
46
|
const present = new Set(Array.isArray(merged.presentAgents) ? merged.presentAgents.map(String) : []);
|
|
47
|
-
|
|
47
|
+
const qaAgentId = typeof merged.qaAgentId === 'string' && merged.qaAgentId.trim().length > 0
|
|
48
|
+
? merged.qaAgentId
|
|
49
|
+
: 'qa-zig';
|
|
50
|
+
return present.has(qaAgentId)
|
|
51
|
+
? `- ${qaAgentId} evidence 존재, QA 수행됨`
|
|
52
|
+
: `- ${qaAgentId} evidence 없음 또는 별도 QA 미실행`;
|
|
48
53
|
}
|
|
49
54
|
export function renderReport(paths, params) {
|
|
50
55
|
const task = loadTask(paths, params.taskId);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ZigrixPaths } from '../state/paths.js';
|
|
2
2
|
import { type ZigrixTask } from '../state/tasks.js';
|
|
3
|
-
export declare const DEFAULT_REQUIRED_AGENTS:
|
|
3
|
+
export declare const DEFAULT_REQUIRED_AGENTS: readonly ["orchestrator", "qa"];
|
|
4
4
|
export declare function resolveRequiredAgents(task: Partial<ZigrixTask> & Record<string, unknown>): string[];
|
|
5
5
|
export declare function prepareWorker(paths: ZigrixPaths, params: {
|
|
6
6
|
taskId: string;
|